From 389c4bed9615fbdf25e242dcd482f7a8b6fbb3bd Mon Sep 17 00:00:00 2001 From: Peter Storch Date: Tue, 30 Jul 2024 15:24:18 +0200 Subject: [PATCH] fix #1957 open MarkerEditActivity to receive external marker (#1960) * fix #1957 add CreateMarkerActivity to receive external marker --- .../opentracks/content/data/TestDataUtil.java | 8 +- .../io/file/importer/ExportImportTest.java | 4 +- .../file/importer/KMLTrackImporterTest.java | 2 +- .../TrackRecordingServiceMarkerTest.java | 152 ------------------ src/main/AndroidManifest.xml | 12 +- .../opentracks/TrackRecordingActivity.java | 8 +- .../opentracks/chart/ChartView.java | 58 ------- .../opentracks/data/ContentProviderUtils.java | 12 -- .../data/CustomSQLiteOpenHelper.java | 28 +++- .../opentracks/data/models/Marker.java | 30 +--- .../opentracks/data/tables/MarkerColumns.java | 6 - .../io/file/importer/TrackImporter.java | 57 +------ .../publicapi/CreateMarkerActivity.java | 39 +++++ .../publicapi/ShowMarkerActivity.java | 57 +++++++ .../services/TrackRecordingManager.java | 34 +--- .../services/TrackRecordingService.java | 13 +- .../ui/markers/MarkerDetailActivity.java | 10 +- .../ui/markers/MarkerEditActivity.java | 82 ++++------ .../ui/markers/MarkerEditViewModel.java | 28 +++- src/main/res/values/strings.xml | 1 + 20 files changed, 226 insertions(+), 415 deletions(-) delete mode 100644 src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceMarkerTest.java create mode 100644 src/main/java/de/dennisguse/opentracks/publicapi/CreateMarkerActivity.java create mode 100644 src/main/java/de/dennisguse/opentracks/publicapi/ShowMarkerActivity.java diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/data/TestDataUtil.java b/src/androidTest/java/de/dennisguse/opentracks/content/data/TestDataUtil.java index e7f3164d8..afd3ebc76 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/data/TestDataUtil.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/data/TestDataUtil.java @@ -88,9 +88,9 @@ public class TestDataUtil { stats.setTotalDistance(Distance.of(0)); stats.setTotalTime(Duration.ofMillis(0)); List markers = List.of( - new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, stats, trackPoints.get(1), null), - new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, stats, trackPoints.get(4), null), - new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, stats, trackPoints.get(5), null) + new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, trackPoints.get(1), null), + new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, trackPoints.get(4), null), + new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, trackPoints.get(5), null) ); return new TrackData(track, trackPoints, markers); @@ -152,7 +152,7 @@ public class TestDataUtil { stats.setTotalDistance(Distance.of(0)); stats.setTotalTime(Duration.ofMillis(0)); - return new Marker("Marker name", "Marker description", "Marker category", "", trackId, stats, trackPoint, photoUrl); + return new Marker("Marker name", "Marker description", "Marker category", "", trackId, trackPoint, photoUrl); } public static List getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) { diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java index 545ab9dc8..d5c8fa660 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java @@ -139,7 +139,7 @@ public class ExportImportTest { Distance sensorDistance = Distance.of(10); // recording distance interval sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3, 14, 10, 13, 15, 10, 1f); - service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null); + contentProviderUtils.insertMarker(new Marker("Marker 1", "Marker 1 desc", "Marker 1 category", null, trackId, service.getLastStoredTrackPointWithLocation(), "")); // A sensor-only TrackPoint trackPointCreator.setClock("2020-02-02T02:02:04Z"); @@ -154,7 +154,7 @@ public class ExportImportTest { mockSensorData(trackPointCreator, 5f, Distance.of(2), 69f, 3f, 50f, null); // Distance will be added to next TrackPoint sendLocation(trackPointCreator, "2020-02-02T02:02:17Z", 3, 14.001, 10, 13, 15, 10, 0f); - service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null); + contentProviderUtils.insertMarker(new Marker("Marker 2", "Marker 2 desc", "Marker 2 category", null, trackId, service.getLastStoredTrackPointWithLocation(), "")); trackPointCreator.setClock("2020-02-02T02:02:18Z"); trackPointCreator.getSensorManager().sensorDataSet = new SensorDataSet(); diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporterTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporterTest.java index 64a3d16b0..427e37bbe 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporterTest.java @@ -166,7 +166,7 @@ public class KMLTrackImporterTest { assertEquals(ActivityType.UNKNOWN, importedTrack.getActivityType()); // 2. markers - assertEquals(0, contentProviderUtils.getMarkers(importTrackId).size()); + assertEquals(1, contentProviderUtils.getMarkers(importTrackId).size()); // 3. trackpoints List importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId); diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceMarkerTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceMarkerTest.java deleted file mode 100644 index 5964089c1..000000000 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceMarkerTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package de.dennisguse.opentracks.services; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import android.content.Context; -import android.content.Intent; -import android.os.Looper; - -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.MediumTest; -import androidx.test.rule.ServiceTestRule; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.time.Instant; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.data.ContentProviderUtils; -import de.dennisguse.opentracks.data.models.Marker; -import de.dennisguse.opentracks.data.models.Track; -import de.dennisguse.opentracks.data.models.TrackPoint; -import de.dennisguse.opentracks.services.handlers.TrackPointCreator; - -@RunWith(AndroidJUnit4.class) -public class TrackRecordingServiceMarkerTest { - - @Rule - public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS); - - private final Context context = ApplicationProvider.getApplicationContext(); - private ContentProviderUtils contentProviderUtils; - - private TrackRecordingService service; - - @BeforeClass - public static void preSetUp() { - // Prepare looper for Android's message queue - if (Looper.myLooper() == null) Looper.prepare(); - } - - @AfterClass - public static void finalTearDown() { - if (Looper.myLooper() != null) Looper.myLooper().quit(); - } - - private TrackRecordingService startService() throws TimeoutException { - Intent startIntent = new Intent(context, TrackRecordingService.class); - return ((TrackRecordingService.Binder) mServiceRule.bindService(startIntent)) - .getService(); - } - - @Before - public void setUp() throws TimeoutException { - contentProviderUtils = new ContentProviderUtils(context); - service = startService(); - tearDown(); - } - - @After - public void tearDown() { - // Ensure that the database is empty after every test - contentProviderUtils.deleteAllTracks(context); - } - - @MediumTest - @Test - public void notRecording_testInsertMarker() { - // given - assertFalse(service.isRecording()); - - // when - Marker.Id markerId = service.insertMarker(null, null, null, null); - - // then - assertNull(markerId); - } - - @MediumTest - @Test - public void recording_noGPSfix_cannotCreateMarker() { - // given - service.startNewTrack(); - - // when - Marker.Id markerId = service.insertMarker(null, null, null, null); - - // then - assertNull(markerId); - } - - @MediumTest - @Test - public void recording_GPSfix_createsMarker() { - // given - TrackPointCreator trackPointCreator = service.getTrackPointCreator(); - - trackPointCreator.setClock("2020-02-02T02:02:02Z"); - Track.Id trackId = service.startNewTrack(); - service.stopUpdateRecordingData(); - - assertTrue(service.isRecording()); - trackPointCreator.onNewTrackPoint( - new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:03Z")) - .setLatitude(10) - .setLongitude(10) - ); - - // when - Marker.Id markerId = service.insertMarker(null, null, null, null); - - // then - assertNotEquals(new Marker.Id(-1L), markerId); - Marker wpt = contentProviderUtils.getMarker(markerId); - assertEquals(context.getString(R.string.marker_icon_url), wpt.getIcon()); - assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName()); - assertEquals(trackId, wpt.getTrackId()); - assertEquals(0.0, wpt.getLength().toM(), 0.01); - assertNotNull(wpt.getLocation()); - - trackPointCreator.setClock("2020-02-02T02:02:04Z"); - service.endCurrentTrack(); - } -} diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 8621f600a..ed26730a3 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -314,7 +314,7 @@ limitations under the License. @@ -322,6 +322,16 @@ limitations under the License. + + + + + + + + diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java index c90217fce..b2310fb11 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java @@ -33,6 +33,7 @@ import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.TrackDataHub; import de.dennisguse.opentracks.data.models.ActivityType; import de.dennisguse.opentracks.data.models.Track; +import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.databinding.TrackRecordingBinding; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment; @@ -269,9 +270,14 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc } if (item.getItemId() == R.id.track_detail_insert_marker) { + TrackPoint trackPoint = trackRecordingServiceConnection.getTrackRecordingService().getLastStoredTrackPointWithLocation(); + if (trackPoint == null) { + return true; + } Intent intent = IntentUtils .newIntent(this, MarkerEditActivity.class) - .putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId); + .putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId) + .putExtra(MarkerEditActivity.EXTRA_LOCATION, trackPoint.getLocation()); startActivity(intent); return true; } diff --git a/src/main/java/de/dennisguse/opentracks/chart/ChartView.java b/src/main/java/de/dennisguse/opentracks/chart/ChartView.java index be630ce0a..ff96cac67 100644 --- a/src/main/java/de/dennisguse/opentracks/chart/ChartView.java +++ b/src/main/java/de/dennisguse/opentracks/chart/ChartView.java @@ -152,31 +152,6 @@ public class ChartView extends View { return true; } - @Override - public boolean onSingleTapConfirmed(MotionEvent event) { - // Check if the y event is within markerHeight of the marker center - if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2f) < markerHeight) { - int minDistance = Integer.MAX_VALUE; - Marker nearestMarker = null; - synchronized (markers) { - for (Marker marker : markers) { - int distance = Math.abs(getX(getMarkerXValue(marker)) - (int) event.getX() - getScrollX()); - if (distance < minDistance) { - minDistance = distance; - nearestMarker = marker; - } - } - } - if (nearestMarker != null && minDistance < markerWidth) { - Intent intent = IntentUtils.newIntent(getContext(), MarkerDetailActivity.class) - .putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, nearestMarker.getId()); - getContext().startActivity(intent); - return true; - } - } - - return false; - } }); private final ScaleGestureDetector detectorZoom = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() { @@ -589,7 +564,6 @@ public class ChartView extends View { clipToGraphArea(canvas); drawDataSeries(canvas); - drawMarker(canvas); drawGrid(canvas); canvas.restore(); @@ -630,30 +604,6 @@ public class ChartView extends View { } } - private void drawMarker(Canvas canvas) { - synchronized (markers) { - for (Marker marker : markers) { - double xValue = getMarkerXValue(marker); - double markerIconSizeInXaxisUnits = maxX*markerWidth/effectiveWidth / zoomLevel; - if (xValue > maxX + markerIconSizeInXaxisUnits * (1-MARKER_X_ANCHOR)) { - continue; // there is no chance that this marker will be visible - } - canvas.save(); - float x = getX(getMarkerXValue(marker)); - canvas.drawLine(x, topBorder + spacer + markerHeight / 2, x, topBorder + effectiveHeight, markerPaint); - // if marker is not near the end of the track then draw it normally - if (xValue < maxX - markerIconSizeInXaxisUnits*(1-MARKER_X_ANCHOR)) { - canvas.translate(x - (markerWidth * MARKER_X_ANCHOR), topBorder + spacer); - } else { // marker at the end needs to be drawn mirrored so that it is more visible - canvas.translate(x + (markerWidth * MARKER_X_ANCHOR), topBorder + spacer); - canvas.scale(-1, 1); - } - markerPin.draw(canvas); - canvas.restore(); - } - } - } - /** * Draws the grid. * @@ -1043,14 +993,6 @@ public class ChartView extends View { return topBorder + yAxisOffset + (int) ((1 - percentage) * rangeHeight); } - private double getMarkerXValue(Marker marker) { - if (chartByDistance) { - return marker.getLength().toKM_Miles(unitSystem); - } else { - return marker.getDuration().toMillis(); - } - } - /** * Gets a paint's Rect for a string. * diff --git a/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java index bc6875850..7d6d9e5b0 100644 --- a/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java @@ -377,8 +377,6 @@ public class ContentProviderUtils { int categoryIndex = cursor.getColumnIndexOrThrow(MarkerColumns.CATEGORY); int iconIndex = cursor.getColumnIndexOrThrow(MarkerColumns.ICON); int trackIdIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TRACKID); - int lengthIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LENGTH); - int durationIndex = cursor.getColumnIndexOrThrow(MarkerColumns.DURATION); int longitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LONGITUDE); int latitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LATITUDE); int timeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TIME); @@ -419,13 +417,6 @@ public class ContentProviderUtils { if (!cursor.isNull(iconIndex)) { marker.setIcon(cursor.getString(iconIndex)); } - if (!cursor.isNull(lengthIndex)) { - marker.setLength(Distance.of(cursor.getFloat(lengthIndex))); - } - if (!cursor.isNull(durationIndex)) { - marker.setDuration(Duration.ofMillis(cursor.getLong(durationIndex))); - } - if (!cursor.isNull(photoUrlIndex)) { marker.setPhotoUrl(cursor.getString(photoUrlIndex)); } @@ -542,9 +533,6 @@ public class ContentProviderUtils { values.put(MarkerColumns.CATEGORY, marker.getCategory()); values.put(MarkerColumns.ICON, marker.getIcon()); values.put(MarkerColumns.TRACKID, marker.getTrackId().id()); - values.put(MarkerColumns.LENGTH, marker.getLength().toM()); - values.put(MarkerColumns.DURATION, marker.getDuration().toMillis()); - values.put(MarkerColumns.LONGITUDE, (int) (marker.getLongitude() * 1E6)); values.put(MarkerColumns.LATITUDE, (int) (marker.getLatitude() * 1E6)); values.put(MarkerColumns.TIME, marker.getTime().toEpochMilli()); diff --git a/src/main/java/de/dennisguse/opentracks/data/CustomSQLiteOpenHelper.java b/src/main/java/de/dennisguse/opentracks/data/CustomSQLiteOpenHelper.java index f0bda8bae..53e09d2a4 100644 --- a/src/main/java/de/dennisguse/opentracks/data/CustomSQLiteOpenHelper.java +++ b/src/main/java/de/dennisguse/opentracks/data/CustomSQLiteOpenHelper.java @@ -29,7 +29,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper { private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName(); - private static final int DATABASE_VERSION = 37; + private static final int DATABASE_VERSION = 38; private final Context context; @@ -81,6 +81,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper { case 35 -> upgradeFrom34to35(db); case 36 -> upgradeFrom35to36(db); case 37 -> upgradeFrom36to37(db); + case 38 -> upgradeFrom37to38(db); default -> throw new RuntimeException("Not implemented: upgrade to " + toVersion); } } @@ -105,6 +106,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper { case 34 -> downgradeFrom35to34(db); case 35 -> downgradeFrom36to35(db); case 36 -> downgradeFrom37to36(db); + case 37 -> downgradeFrom38to37(db); default -> throw new RuntimeException("Not implemented: downgrade to " + toVersion); } } @@ -644,4 +646,28 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper { db.endTransaction(); } + private void upgradeFrom37to38(SQLiteDatabase db) { + db.beginTransaction(); + + db.execSQL("ALTER TABLE markers RENAME TO markers_old"); + db.execSQL("CREATE TABLE markers (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, bearing FLOAT, photoUrl TEXT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)"); + db.execSQL("INSERT INTO markers SELECT _id, name, description, category, icon, trackid, longitude, latitude, time, elevation, accuracy, bearing, photoUrl FROM markers_old"); + db.execSQL("DROP TABLE markers_old"); + + db.execSQL("CREATE INDEX markers_trackid_index ON markers(trackid)"); + + db.setTransactionSuccessful(); + db.endTransaction(); + } + + private void downgradeFrom38to37(SQLiteDatabase db) { + db.beginTransaction(); + + db.execSQL("ALTER TABLE markers ADD COLUMN length FLOAT"); + db.execSQL("ALTER TABLE markers ADD COLUMN duration INTEGER"); + + db.setTransactionSuccessful(); + db.endTransaction(); + } + } diff --git a/src/main/java/de/dennisguse/opentracks/data/models/Marker.java b/src/main/java/de/dennisguse/opentracks/data/models/Marker.java index e25e8a1ff..7c328077c 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/Marker.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/Marker.java @@ -27,8 +27,6 @@ import androidx.annotation.Nullable; import java.time.Duration; import java.time.Instant; -import de.dennisguse.opentracks.stats.TrackStatistics; - /** * NOTE: A marker is indirectly (via it's location) assigned to one {@link TrackPoint} with trackPoint.hasLocation() == true. * @@ -53,10 +51,6 @@ public final class Marker { private Altitude altitude; private Float bearing; - //TODO It is the distance from the track starting point; rename to something more meaningful - private Distance length; - private Duration duration; - @Deprecated //TODO Make an URI instead of String private String photoUrl = ""; @@ -65,7 +59,6 @@ public final class Marker { this.time = time; } - @Deprecated //TODO Marker cannot be created without length AND duration! public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) { this.trackId = trackId; @@ -75,20 +68,15 @@ public final class Marker { throw new RuntimeException("Marker requires a trackpoint with a location."); setTrackPoint(trackPoint); - - this.length = Distance.of(0); //TODO Not cool! - this.duration = Duration.ofMillis(0); //TODO Not cool! } @Deprecated - public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, @NonNull TrackStatistics statistics, @NonNull TrackPoint trackPoint, String photoUrl) { + public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, @NonNull TrackPoint trackPoint, String photoUrl) { this(trackId, trackPoint); this.name = name; this.description = description; this.category = category; this.icon = icon; - this.length = statistics.getTotalDistance(); - this.duration = statistics.getTotalTime(); this.photoUrl = photoUrl; } @@ -234,22 +222,6 @@ public final class Marker { this.bearing = bearing; } - public Distance getLength() { - return length; - } - - public void setLength(Distance length) { - this.length = length; - } - - public Duration getDuration() { - return duration; - } - - public void setDuration(@NonNull Duration duration) { - this.duration = duration; - } - public String getPhotoUrl() { return photoUrl; } diff --git a/src/main/java/de/dennisguse/opentracks/data/tables/MarkerColumns.java b/src/main/java/de/dennisguse/opentracks/data/tables/MarkerColumns.java index fd472f344..2d3065f46 100644 --- a/src/main/java/de/dennisguse/opentracks/data/tables/MarkerColumns.java +++ b/src/main/java/de/dennisguse/opentracks/data/tables/MarkerColumns.java @@ -41,10 +41,6 @@ public interface MarkerColumns extends BaseColumns { String CATEGORY = "category"; // marker category String ICON = "icon"; // marker icon String TRACKID = "trackid"; // track id - - String LENGTH = "length"; // length of the track (without smoothing) - String DURATION = "duration"; // total duration of the track from the beginning until now - String LONGITUDE = "longitude"; // longitude String LATITUDE = "latitude"; // latitude String TIME = "time"; // time @@ -61,8 +57,6 @@ public interface MarkerColumns extends BaseColumns { + CATEGORY + " TEXT, " + ICON + " TEXT, " + TRACKID + " INTEGER NOT NULL, " - + LENGTH + " FLOAT, " - + DURATION + " INTEGER, " + LONGITUDE + " INTEGER, " + LATITUDE + " INTEGER, " + TIME + " INTEGER, " diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java index 8bc2d07c2..e2fd7f10e 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java @@ -16,7 +16,6 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.ContentProviderUtils; @@ -26,7 +25,6 @@ import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.TrackPoint; -import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatisticsUpdater; import de.dennisguse.opentracks.ui.markers.MarkerUtils; import de.dennisguse.opentracks.util.FileUtils; @@ -158,7 +156,7 @@ public class TrackImporter { contentProviderUtils.bulkInsertTrackPoint(trackPoints, trackId); // Store Markers - matchMarkers2TrackPoints(trackId); + updateMarkers(trackId); for (Marker marker : markers) marker.setTrackId(trackId); //TODO Should happen in bulkInsertMarkers @@ -218,55 +216,16 @@ public class TrackImporter { } /** - * NOTE: Modifies content of markers (incl. removal). + * NOTE: Modifies content of markers. */ - private void matchMarkers2TrackPoints(Track.Id trackId) { - List trackPointsWithLocation = trackPoints.stream() - .filter(TrackPoint::hasLocation) - .collect(Collectors.toList()); - - List todoMarkers = new LinkedList<>(markers); - List doneMarkers = new LinkedList<>(); - - for (final TrackPoint trackPoint : trackPointsWithLocation) { - if (todoMarkers.isEmpty()) { - break; + private void updateMarkers(Track.Id trackId) { + markers.forEach(marker -> { + if (marker.hasPhoto()) { + marker.setPhotoUrl(getInternalPhotoUrl(trackId, marker.getPhotoUrl())); } - TrackStatisticsUpdater updater = new TrackStatisticsUpdater(); - updater.addTrackPoint(trackPoint); - - List matchedMarkers = todoMarkers.stream() - .filter(it -> trackPoint.getLatitude() == it.getLatitude() - && trackPoint.getLongitude() == it.getLongitude() - && trackPoint.getTime().equals(it.getTime()) - ) - .collect(Collectors.toList()); - - TrackStatistics statistics = updater.getTrackStatistics(); - for (Marker marker : matchedMarkers) { - if (marker.hasPhoto()) { - marker.setPhotoUrl(getInternalPhotoUrl(trackId, marker.getPhotoUrl())); - } - - marker.setIcon(context.getString(R.string.marker_icon_url)); //TODO Why? - - marker.setLength(statistics.getTotalDistance()); - marker.setDuration(statistics.getTotalTime()); - - marker.setTrackPoint(trackPoint); - } - - todoMarkers.removeAll(matchedMarkers); - doneMarkers.addAll(matchedMarkers); - } - - if (todoMarkers.isEmpty()) { - Log.w(TAG, "Some markers could not be attached to TrackPoints; those are not imported."); - } - - markers.clear(); - markers.addAll(doneMarkers); + marker.setIcon(context.getString(R.string.marker_icon_url)); //TODO Why? + }); } /** diff --git a/src/main/java/de/dennisguse/opentracks/publicapi/CreateMarkerActivity.java b/src/main/java/de/dennisguse/opentracks/publicapi/CreateMarkerActivity.java new file mode 100644 index 000000000..501f7630c --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/publicapi/CreateMarkerActivity.java @@ -0,0 +1,39 @@ +package de.dennisguse.opentracks.publicapi; + +import android.content.Intent; +import android.location.Location; +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +import de.dennisguse.opentracks.data.models.Track; +import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; +import de.dennisguse.opentracks.ui.markers.MarkerEditActivity; +import de.dennisguse.opentracks.util.IntentUtils; + +/** + * Public API to creates a Marker for a given track with a given location + */ +public class CreateMarkerActivity extends AppCompatActivity { + + public static final String EXTRA_TRACK_ID = "track_id"; + public static final String EXTRA_LOCATION = "location"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Track.Id trackId = new Track.Id(getIntent().getLongExtra(EXTRA_TRACK_ID, 0L)); + Location location = getIntent().getParcelableExtra(EXTRA_LOCATION); + + TrackRecordingServiceConnection.execute(this, (service, self) -> { + Intent intent = IntentUtils + .newIntent(this, MarkerEditActivity.class) + .putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId) + .putExtra(MarkerEditActivity.EXTRA_LOCATION, location); + startActivity(intent); + finish(); + }); + } + +} diff --git a/src/main/java/de/dennisguse/opentracks/publicapi/ShowMarkerActivity.java b/src/main/java/de/dennisguse/opentracks/publicapi/ShowMarkerActivity.java new file mode 100644 index 000000000..07b47ef3e --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/publicapi/ShowMarkerActivity.java @@ -0,0 +1,57 @@ +package de.dennisguse.opentracks.publicapi; + +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import java.util.ArrayList; +import java.util.List; + +import de.dennisguse.opentracks.AbstractActivity; +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.data.ContentProviderUtils; +import de.dennisguse.opentracks.data.models.Marker; +import de.dennisguse.opentracks.databinding.MarkerDetailActivityBinding; +import de.dennisguse.opentracks.ui.markers.DeleteMarkerDialogFragment.DeleteMarkerCaller; +import de.dennisguse.opentracks.ui.markers.MarkerDetailActivity; +import de.dennisguse.opentracks.ui.markers.MarkerDetailFragment; +import de.dennisguse.opentracks.util.IntentUtils; + +/** + * Public api to show an existing marker + */ +public class ShowMarkerActivity extends AppCompatActivity { + + public static final String EXTRA_MARKER_ID = "marker_id"; + + private static final String TAG = ShowMarkerActivity.class.getSimpleName(); + + private MarkerDetailActivityBinding viewBinding; + + private List markerIds; + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + + Marker.Id markerId = new Marker.Id(getIntent().getLongExtra(EXTRA_MARKER_ID, 0)); + if (markerId.id() == 0) { + throw new IllegalStateException("No valid markerId provided"); + } + Intent intent = IntentUtils.newIntent(this, MarkerDetailActivity.class) + .putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, markerId); + startActivity(intent); + finish(); + } + +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java index e12152cfe..d2fd761cf 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java @@ -1,10 +1,8 @@ package de.dennisguse.opentracks.services; -import android.content.ContentUris; import android.content.Context; import android.content.SharedPreferences; import android.database.sqlite.SQLiteException; -import android.net.Uri; import android.os.Handler; import android.util.Log; import android.util.Pair; @@ -19,7 +17,6 @@ import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.models.ActivityType; import de.dennisguse.opentracks.data.models.Distance; -import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; @@ -138,31 +135,6 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen return new Pair<>(track, current); } - public Marker.Id insertMarker(String name, String category, String description, String photoUrl) { - if (name == null) { - Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(trackId); - if (nextMarkerNumber == null) { - nextMarkerNumber = 1; - } - name = context.getString(R.string.marker_name_format, nextMarkerNumber + 1); - } - - if (lastStoredTrackPointWithLocation == null) { - Log.i(TAG, "Could not create a marker as trackPoint is unknown."); - return null; - } - - category = category != null ? category : ""; - description = description != null ? description : ""; - String icon = context.getString(R.string.marker_icon_url); - photoUrl = photoUrl != null ? photoUrl : ""; - - // Insert marker - Marker marker = new Marker(name, description, category, icon, trackId, getTrackStatistics(), lastStoredTrackPointWithLocation, photoUrl); - Uri uri = contentProviderUtils.insertMarker(marker); - return new Marker.Id(ContentUris.parseId(uri)); - } - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public void onIdle() { Log.d(TAG, "Becoming idle"); @@ -247,7 +219,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen } TrackStatistics getTrackStatistics() { - return trackStatisticsUpdater.getTrackStatistics(); + return trackStatisticsUpdater == null ? null : trackStatisticsUpdater.getTrackStatistics(); } private void insertTrackPoint(@NonNull TrackPoint trackPoint, boolean storeLastTrackPointIfUseful) { @@ -308,6 +280,10 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen } } + public TrackPoint getLastStoredTrackPointWithLocation() { + return lastStoredTrackPointWithLocation; + } + public interface IdleObserver { void onIdle(); } diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index 3e5ef57eb..4481a7f78 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -37,7 +37,6 @@ import java.io.StringWriter; import java.time.Duration; import de.dennisguse.opentracks.data.models.Distance; -import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; @@ -57,6 +56,10 @@ public class TrackRecordingService extends Service implements TrackPointCreator. public static final RecordingData NOT_RECORDING = new RecordingData(null, null, null); public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE; + public TrackPoint getLastStoredTrackPointWithLocation() { + return trackRecordingManager.getLastStoredTrackPointWithLocation(); + } + public class Binder extends android.os.Binder { private Binder() { @@ -269,14 +272,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator. gpsStatusObservable.postValue(gpsStatusValue); } - public Marker.Id insertMarker(String name, String category, String description, String photoUrl) { - if (!isRecording()) { - return null; - } - - return trackRecordingManager.insertMarker(name, category, description, photoUrl); - } - @Deprecated @VisibleForTesting public TrackPointCreator getTrackPointCreator() { diff --git a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerDetailActivity.java b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerDetailActivity.java index 196254119..afcfcfd8a 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerDetailActivity.java +++ b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerDetailActivity.java @@ -59,15 +59,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark protected void onCreate(Bundle bundle) { super.onCreate(bundle); - Object bundleMarkerId = getIntent().getExtras().get(EXTRA_MARKER_ID); - Marker.Id markerId = null; - if (bundleMarkerId instanceof Marker.Id cast) { - markerId = cast; - } - if (bundleMarkerId instanceof Long cast) { - //Incoming Intent via Dashboard API. - markerId = new Marker.Id(cast); - } + Marker.Id markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID); if (markerId == null) { Log.d(TAG, "invalid marker id"); finish(); diff --git a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerEditActivity.java b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerEditActivity.java index b80ba6753..40a272982 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerEditActivity.java +++ b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerEditActivity.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.location.Location; import android.net.Uri; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -36,19 +37,19 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.PickVisualMediaRequest; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModelProvider; import java.io.FileDescriptor; import java.io.IOException; +import java.time.Instant; import de.dennisguse.opentracks.AbstractActivity; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Track; +import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.databinding.MarkerEditBinding; -import de.dennisguse.opentracks.services.TrackRecordingService; -import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; /** * An activity to add/edit a marker. @@ -59,13 +60,14 @@ public class MarkerEditActivity extends AbstractActivity { public static final String EXTRA_TRACK_ID = "track_id"; public static final String EXTRA_MARKER_ID = "marker_id"; + public static final String EXTRA_LOCATION = "location"; private static final String CAMERA_PHOTO_URI_KEY = "camera_photo_uri_key"; - private static final String NEW_MARKER_ID = "new_marker_id"; - private static final String TAG = MarkerEditActivity.class.getSimpleName(); private Track.Id trackId; + private Location location; + private Marker.Id markerId; private Marker marker; private MenuItem insertPhotoMenuItem; @@ -93,16 +95,16 @@ public class MarkerEditActivity extends AbstractActivity { super.onCreate(savedInstanceState); trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID); - @Nullable Marker.Id markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID); - final boolean isNewMarker = markerId == null; + location = getIntent().getParcelableExtra(EXTRA_LOCATION); + markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID); + if ((trackId == null || location == null) && markerId == null) { + throw new IllegalStateException("TrackId and Location must be provided or an existing markerId"); + } if (savedInstanceState != null) { cameraPhotoUri = Uri.parse(savedInstanceState.getString(CAMERA_PHOTO_URI_KEY, "")); - Marker.Id newMarkerId = savedInstanceState.getParcelable(NEW_MARKER_ID); - if (newMarkerId != null) { - markerId = newMarkerId; - } } + boolean isNewMarker = markerId == null; hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY); @@ -157,16 +159,8 @@ public class MarkerEditActivity extends AbstractActivity { } }); - - if (markerId == null) { - TrackRecordingServiceConnection.execute(this, (service, self) -> { - Marker.Id newMarkerId = createNewMarker(service); - if (newMarkerId == null) { - finish(); - } else { - loadMarkerData(newMarkerId); - } - }); + if (isNewMarker) { + createNewMarker().observe(this, this::loadMarkerData); } else { loadMarkerData(markerId); } @@ -174,35 +168,27 @@ public class MarkerEditActivity extends AbstractActivity { setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar); } - private Marker.Id createNewMarker(TrackRecordingService trackRecordingService) { - try { - Marker.Id marker = trackRecordingService.insertMarker("", "", "", null); - if (marker == null) { - Toast.makeText(this, R.string.marker_add_error, Toast.LENGTH_LONG).show(); - return null; - } - - return marker; - } catch (IllegalStateException e) { - Log.e(TAG, "Unable to add marker.", e); - return null; - } + private LiveData createNewMarker() { + TrackPoint trackPoint = new TrackPoint(location, Instant.now()); + return viewModel.createNewMarker(trackId, trackPoint); } private void loadMarkerData(Marker.Id markerId) { - viewModel.getMarkerData(markerId).observe(this, data -> { - marker = data; - viewBinding.markerEditName.setText(marker.getName()); - viewBinding.markerEditMarkerType.setText(marker.getCategory()); - viewBinding.markerEditDescription.setText(marker.getDescription()); - if (marker.hasPhoto()) { - setMarkerImageView(marker.getPhotoURI()); - } else { - viewBinding.markerEditPhoto.setImageDrawable(null); - } + viewModel.getMarkerData(markerId).observe(this, this::loadMarkerData); + } - hideAndShowOptions(); - }); + private void loadMarkerData(Marker data) { + marker = data; + viewBinding.markerEditName.setText(marker.getName()); + viewBinding.markerEditMarkerType.setText(marker.getCategory()); + viewBinding.markerEditDescription.setText(marker.getDescription()); + if (marker.hasPhoto()) { + setMarkerImageView(marker.getPhotoURI()); + } else { + viewBinding.markerEditPhoto.setImageDrawable(null); + } + + hideAndShowOptions(); } @Override @@ -210,6 +196,8 @@ public class MarkerEditActivity extends AbstractActivity { super.onDestroy(); trackId = null; + location = null; + markerId = null; viewBinding = null; viewModel = null; takePictureFromGallery = null; @@ -222,8 +210,6 @@ public class MarkerEditActivity extends AbstractActivity { if (cameraPhotoUri != null) { outState.putString(CAMERA_PHOTO_URI_KEY, cameraPhotoUri.toString()); } - - outState.putParcelable(NEW_MARKER_ID, marker.getId()); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerEditViewModel.java b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerEditViewModel.java index 832d9baee..3bf9b2b04 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerEditViewModel.java +++ b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerEditViewModel.java @@ -20,6 +20,8 @@ import java.util.NoSuchElementException; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.models.Marker; +import de.dennisguse.opentracks.data.models.Track; +import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.util.FileUtils; public class MarkerEditViewModel extends AndroidViewModel { @@ -122,22 +124,40 @@ public class MarkerEditViewModel extends AndroidViewModel { marker.setName(name); marker.setCategory(category); marker.setDescription(description); - new ContentProviderUtils(getApplication()).updateMarker(getApplication(), marker); + if (marker.getId() == null) { + new ContentProviderUtils(getApplication()).insertMarker(marker); + } else { + new ContentProviderUtils(getApplication()).updateMarker(getApplication(), marker); + } if (photoOriginalUri != null && (!marker.hasPhoto() || !photoOriginalUri.equals(marker.getPhotoURI()))) { deletePhoto(photoOriginalUri); } } + public LiveData createNewMarker(Track.Id trackId, TrackPoint trackPoint) { + Integer nextMarkerNumber = new ContentProviderUtils(getApplication()).getNextMarkerNumber(trackId); + if (nextMarkerNumber == null) { + nextMarkerNumber = 1; + } + String name = getApplication().getString(R.string.marker_name_format, nextMarkerNumber + 1); + String icon = getApplication().getString(R.string.marker_icon_url); + + Marker marker = new Marker(name, "", "", icon, trackId, trackPoint, ""); + + if (markerData == null) { + markerData = new MutableLiveData<>(); + } + markerData.postValue(marker); + return markerData; + } + public void onCancel(boolean isNewMarker) { Marker marker = getMarker(); if (isNewMarker) { // it's new marker -> clean all photos. deletePhoto(marker); deletePhoto(photoOriginalUri); - - new ContentProviderUtils(getApplication()).deleteMarker(getApplication(), marker.getId()); - } else if (photoOriginalUri == null || (marker.hasPhoto() && !marker.getPhotoURI().equals(photoOriginalUri))) { // it's an edit marker -> delete photo if it was empty or it was changed (leaving the original in that case). deletePhoto(marker); diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 32e8daf54..9ed8d5a89 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -258,6 +258,7 @@ limitations under the License. Already exists Errors + Unable to create a marker. Try again. Unable to insert a marker. No GPS signal. Try again. A marker was inserted Canceled, no marker added