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 static TrackData createTestingTrack(Track.Id trackId) { 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 static Marker createMarkerWithPhoto(Context context, Track.Id trackId, Tr 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 f86bad646..bbc284614 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 @@ -141,7 +141,7 @@ public void setUp() throws TimeoutException { 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"); @@ -156,7 +156,7 @@ public void setUp() throws TimeoutException { 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 void kml22_with_statistics_marker() throws IOException { 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.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 boolean onOptionsItemSelected(MenuItem item) { } 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 ef7285aba..223c6dd12 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 boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float ve 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 @@ protected void onDraw(Canvas canvas) { clipToGraphArea(canvas); drawDataSeries(canvas); - drawMarker(canvas); drawGrid(canvas); canvas.restore(); @@ -630,30 +604,6 @@ private void drawDataSeries(Canvas canvas) { } } - 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. * @@ -1042,14 +992,6 @@ private int getY(ChartValueSeries chartValueSeries, double value) { 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 Marker createMarker(Cursor cursor) { 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 Marker createMarker(Cursor cursor) { 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 @@ ContentValues createContentValues(@NonNull Marker marker) { 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 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 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 @@ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 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 @@ private void downgradeFrom37to36(SQLiteDatabase db) { 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 0a72027c0..40afdf5c1 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 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 Marker(@Nullable Track.Id trackId, Instant time) { 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 Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) { 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 void setBearing(float bearing) { 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 49bfa109c..c84b00b72 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.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.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 @@ private void finishTrack() { contentProviderUtils.bulkInsertTrackPoint(trackPoints, trackId); // Store Markers - matchMarkers2TrackPoints(trackId); + updateMarkers(trackId); for (Marker marker : markers) marker.setTrackId(trackId); //TODO Should happen in bulkInsertMarkers @@ -217,55 +215,16 @@ private void adjustTrackPoints() { } /** - * 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; - } - - 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); + private void updateMarkers(Track.Id trackId) { + markers.forEach(marker -> { + if (marker.hasPhoto()) { + marker.setPhotoUrl(getInternalPhotoUrl(trackId, marker.getPhotoUrl())); } - 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.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 @@ Pair> getDataForUI() { 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 @@ synchronized boolean onNewTrackPoint(@NonNull TrackPoint trackPoint) { } TrackStatistics getTrackStatistics() { - return trackStatisticsUpdater.getTrackStatistics(); + return trackStatisticsUpdater == null ? null : trackStatisticsUpdater.getTrackStatistics(); } private void insertTrackPoint(@NonNull TrackPoint trackPoint, boolean storeLastTrackPointIfUseful) { @@ -308,6 +280,10 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin } } + 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.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 void newGpsStatus(GpsStatusValue gpsStatusValue) { 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.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.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 @@ protected void onCreate(Bundle savedInstanceState) { 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 @@ protected void onCreate(Bundle savedInstanceState) { } }); - - 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 @@ protected void onCreate(Bundle savedInstanceState) { 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 @@ protected void onDestroy() { super.onDestroy(); trackId = null; + location = null; + markerId = null; viewBinding = null; viewModel = null; takePictureFromGallery = null; @@ -222,8 +210,6 @@ protected void onSaveInstanceState(@NonNull Bundle outState) { 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 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 void onDone(String name, String category, String description) { 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