From e09b04308c5d62173458e7f803d39a2ba33bab91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20Mart=C3=ADnez?= Date: Tue, 23 Mar 2021 20:08:25 +0100 Subject: [PATCH] Track recording service handles the status of recording: track's id, pause and GPS status. Created an interface for all components that want to know about recording status through the service. All interface's methods are optional so every component can implement what they need. Fixes #388. --- .../CustomContentProviderUtilsTest.java | 2 +- .../io/file/importer/ExportImportTest.java | 9 +- .../services/TrackRecordingServiceTest.java | 55 +++---- .../TrackRecordingServiceTestLooper.java | 51 ++++--- .../opentracks/AbstractListActivity.java | 6 +- .../opentracks/MarkerListActivity.java | 58 ++++---- .../de/dennisguse/opentracks/Startup.java | 12 +- .../opentracks/TrackController.java | 4 +- .../opentracks/TrackListActivity.java | 85 ++++++----- .../opentracks/TrackRecordedActivity.java | 29 +++- .../opentracks/TrackRecordingActivity.java | 59 ++++---- .../opentracks/content/TrackDataHub.java | 54 +++---- .../provider/ContentProviderUtils.java | 6 +- .../io/file/importer/KmzTrackImporter.java | 14 -- .../services/TrackRecordingService.java | 134 +++++------------- .../services/TrackRecordingServiceBinder.java | 12 +- .../TrackRecordingServiceCallback.java | 10 -- .../TrackRecordingServiceConnection.java | 34 ++--- .../TrackRecordingServiceInterface.java | 15 +- .../services/TrackRecordingServiceStatus.java | 79 +++++++++++ .../tasks/AnnouncementPeriodicTask.java | 11 +- .../opentracks/settings/SettingsActivity.java | 49 +++++-- .../opentracks/util/PreferencesUtils.java | 70 --------- src/main/res/values/preferences_internal.xml | 23 --- src/main/res/values/settings.xml | 3 + src/main/res/xml/settings.xml | 8 +- 26 files changed, 426 insertions(+), 466 deletions(-) delete mode 100644 src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceCallback.java create mode 100644 src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceStatus.java delete mode 100644 src/main/res/values/preferences_internal.xml diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java index 345048207..a19bb0a85 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java @@ -606,7 +606,7 @@ public class CustomContentProviderUtilsTest { contentProviderUtils.insertMarker(marker3); contentProviderUtils.insertMarker(marker4); - assertEquals(4, contentProviderUtils.getNextMarkerNumber(trackId)); + assertEquals(Integer.valueOf(4), contentProviderUtils.getNextMarkerNumber(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 66128021a..6c63ed9d1 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 @@ -6,6 +6,7 @@ import android.content.SharedPreferences; import android.os.Looper; import android.util.Log; +import androidx.preference.PreferenceManager; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.LargeTest; import androidx.test.rule.GrantPermissionRule; @@ -215,7 +216,9 @@ public class ExportImportTest { @Test(expected = ImportAlreadyExistsException.class) public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() { // given - PreferencesUtils.setBoolean(sharedPreferences, context, R.string.import_prevent_reimport_key, true); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putBoolean(context.getString(R.string.import_prevent_reimport_key), true); + editor.commit(); Track track = contentProviderUtils.getTrack(trackId); TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context); @@ -285,7 +288,9 @@ public class ExportImportTest { @Test(expected = ImportAlreadyExistsException.class) public void gpx_duplicate_trackUUID() { // given - PreferencesUtils.setBoolean(sharedPreferences, context, R.string.import_prevent_reimport_key, true); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putBoolean(context.getString(R.string.import_prevent_reimport_key), true); + editor.commit(); Track track = contentProviderUtils.getTrack(trackId); TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context); diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java index 233b5d340..643c23737 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java @@ -38,6 +38,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.TemporalUnit; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; @@ -151,31 +154,30 @@ public class TrackRecordingServiceTest { // then // Test if we start in no-recording mode by default. assertFalse(service.isRecording()); - assertNull(service.getRecordingTrackId()); } @MediumTest @Test public void testRecording_oldTracks() throws TimeoutException { // given - createDummyTrack(trackId, false); + createDummyTrack(trackId); // when TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); // then assertFalse(service.isRecording()); - assertNull(service.getRecordingTrackId()); } @MediumTest @Test public void testRecording_serviceRestart_whileRecording() throws TimeoutException { // given - createDummyTrack(trackId, true); + createDummyTrack(trackId); //when TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); + service.resumeTrack(trackId); // then assertTrue(service.isRecording()); @@ -233,7 +235,6 @@ public class TrackRecordingServiceTest { // then assertTrue(service.isRecording()); - assertEquals(trackId, service.getRecordingTrackId()); List trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId); assertEquals(3, trackPoints.size()); @@ -259,7 +260,6 @@ public class TrackRecordingServiceTest { // then assertTrue(service.isRecording()); - assertEquals(trackId, service.getRecordingTrackId()); List trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId); assertEquals(4, trackPoints.size()); @@ -285,7 +285,6 @@ public class TrackRecordingServiceTest { // then assertFalse(service.isRecording()); - assertNull(service.getRecordingTrackId()); List trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId); assertEquals(2, trackPoints.size()); @@ -293,41 +292,20 @@ public class TrackRecordingServiceTest { assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType()); } - @Ignore("Sometimes fails on CI.") - @MediumTest - @Test - public void testRecording_orphanedRecordingTrack() throws TimeoutException { - // given - TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); - - // when - // Just set recording track to a bogus value. - // Make sure that the service will not start recording and will clear the bogus track. - PreferencesUtils.setLong(sharedPreferences, context, R.string.recording_track_id_key, 123L); - - // then - assertFalse(service.isRecording()); - assertNull(service.getRecordingTrackId()); - } - @MediumTest @Test public void testStartNewTrack_alreadyRecording() throws TimeoutException { // given TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); - service.startNewTrack(); + Track.Id trackId = service.startNewTrack(); assertTrue(service.isRecording()); - Track.Id trackId = service.getRecordingTrackId(); - // when Track.Id newTrackId = service.startNewTrack(); // then + assertNotNull(trackId); assertNull(newTrackId); - - assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context)); - assertEquals(trackId, service.getRecordingTrackId()); } @MediumTest @@ -342,8 +320,7 @@ public class TrackRecordingServiceTest { service.endCurrentTrack(); // then - assertFalse(PreferencesUtils.isRecording(sharedPreferences, context)); - assertNull(service.getRecordingTrackId()); + assertFalse(service.isRecording()); } @MediumTest @@ -365,10 +342,9 @@ public class TrackRecordingServiceTest { public void testInsertWaypointMarker_validWaypoint() throws TimeoutException, InterruptedException { // given TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); - service.startNewTrack(); + Track.Id trackId = service.startNewTrack(); assertTrue(service.isRecording()); newTrackPoint(service); - Track.Id trackId = service.getRecordingTrackId(); // when Marker.Id markerId = service.insertMarker(null, null, null, null); @@ -385,22 +361,23 @@ public class TrackRecordingServiceTest { service.endCurrentTrack(); } - private void addTrack(Track track, boolean isRecording) { + private void addTrack(Track track) { assertNotNull(track.getId()); contentProviderUtils.insertTrack(track); assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId()); - PreferencesUtils.setLong(sharedPreferences, context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); - PreferencesUtils.setBoolean(sharedPreferences, context, R.string.recording_track_paused_key, !isRecording); } // NOTE: Do not use to create a track that is currently recording. - private void createDummyTrack(Track.Id id, boolean isRecording) { + private void createDummyTrack(Track.Id id) { Track dummyTrack = new Track(); dummyTrack.setId(id); dummyTrack.setName("Dummy Track"); TrackStatistics trackStatistics = new TrackStatistics(); + Instant now = Instant.now(); + trackStatistics.setStartTime(now.minusSeconds(5L)); + trackStatistics.setStopTime(now.minusSeconds(1L)); dummyTrack.setTrackStatistics(trackStatistics); - addTrack(dummyTrack, isRecording); + addTrack(dummyTrack); } private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException { diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java index d44f84bfa..49b5613bd 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java @@ -6,6 +6,7 @@ import android.content.SharedPreferences; import android.location.Location; import android.os.Looper; +import androidx.preference.PreferenceManager; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; @@ -35,7 +36,6 @@ import de.dennisguse.opentracks.util.PreferencesUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; /** @@ -99,28 +99,36 @@ public class TrackRecordingServiceTestLooper { @MediumTest @Test public void testWithProperties_minRequiredAccuracy() throws TimeoutException { - PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, 500); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putInt(context.getString(R.string.recording_gps_accuracy_key), 500); + editor.commit(); fullRecordingSession(); } @MediumTest @Test public void testWithProperties_voiceFrequencyDefault() throws TimeoutException { - PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default))); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putInt(context.getString(R.string.voice_frequency_key), Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default))); + editor.commit(); fullRecordingSession(); } @MediumTest @Test public void testWithProperties_voiceFrequencyByDistance() throws TimeoutException { - PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, -1); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putInt(context.getString(R.string.voice_frequency_key), -1); + editor.commit(); fullRecordingSession(); } @MediumTest @Test public void testWithProperties_voiceFrequencyByTime() throws TimeoutException { - PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, 1); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putInt(context.getString(R.string.voice_frequency_key), 1); + editor.commit(); fullRecordingSession(); } @@ -128,29 +136,36 @@ public class TrackRecordingServiceTestLooper { @MediumTest @Test public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException { - PreferencesUtils.setInt(sharedPreferences, context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default))); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putInt(context.getString(R.string.max_recording_distance_key), Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default))); + editor.commit(); fullRecordingSession(); } @MediumTest @Test public void testWithProperties_maxRecordingDistance() throws TimeoutException { - PreferencesUtils.setInt(sharedPreferences, context, R.string.max_recording_distance_key, 50); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putInt(context.getString(R.string.max_recording_distance_key), 50); + editor.commit(); fullRecordingSession(); } @MediumTest @Test public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException { - int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)); - PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_distance_interval_key, minRecordingIntervalDefault); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putInt(context.getString(R.string.recording_distance_interval_key), Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default))); + editor.commit(); fullRecordingSession(); } @MediumTest @Test public void testWithProperties_minRecordingDistance() throws TimeoutException { - PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_distance_interval_key, 2); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putInt(context.getString(R.string.recording_distance_interval_key), 2); + editor.commit(); fullRecordingSession(); } @@ -171,22 +186,27 @@ public class TrackRecordingServiceTestLooper { @MediumTest @Test public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException { - PreferencesUtils.setInt(sharedPreferences, context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default))); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putInt(context.getString(R.string.min_recording_interval_key), Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default))); + editor.commit(); fullRecordingSession(); } @MediumTest @Test public void testWithProperties_minRecordingInterval() throws TimeoutException { - PreferencesUtils.setInt(sharedPreferences, context, R.string.min_recording_interval_key, 2); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putInt(context.getString(R.string.min_recording_interval_key), 2); + editor.commit(); fullRecordingSession(); } @MediumTest @Test public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException { - int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default)); - PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putInt(context.getString(R.string.recording_gps_accuracy_key), Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default))); + editor.commit(); fullRecordingSession(); } @@ -209,8 +229,6 @@ public class TrackRecordingServiceTestLooper { Track track = contentProviderUtils.getTrack(trackId); assertNotNull(track); assertEquals(trackId, track.getId()); - assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context)); - assertEquals(trackId, service.getRecordingTrackId()); // Insert a few points, markers and statistics. long startTime = System.currentTimeMillis(); @@ -234,7 +252,6 @@ public class TrackRecordingServiceTestLooper { // Stop the track. Validate if it has correct data. service.endCurrentTrack(); assertFalse(service.isRecording()); - assertNull(service.getRecordingTrackId()); track = contentProviderUtils.getTrack(trackId); assertNotNull(track); assertEquals(trackId, track.getId()); diff --git a/src/main/java/de/dennisguse/opentracks/AbstractListActivity.java b/src/main/java/de/dennisguse/opentracks/AbstractListActivity.java index e63be345b..65716774b 100644 --- a/src/main/java/de/dennisguse/opentracks/AbstractListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/AbstractListActivity.java @@ -26,7 +26,6 @@ import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.util.IntentUtils; -import de.dennisguse.opentracks.util.PreferencesUtils; /** * An abstract class for the following common tasks across @@ -44,6 +43,8 @@ public abstract class AbstractListActivity extends AbstractActivity implements C protected static final int GPS_REQUEST_CODE = 6; private static final int DELETE_REQUEST_CODE = 3; + protected Track.Id recordingTrackId; + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == DELETE_REQUEST_CODE) { @@ -66,7 +67,6 @@ public abstract class AbstractListActivity extends AbstractActivity implements C public void onConfirmDeleteDone(Track.Id... trackIds) { boolean stopRecording = false; - Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(PreferencesUtils.getSharedPreferences(this), this); for (Track.Id trackId : trackIds) { if (trackId.equals(recordingTrackId)) { stopRecording = true; @@ -75,7 +75,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C } if (stopRecording) { - getTrackRecordingServiceConnection().stopRecording(this, false); + getTrackRecordingServiceConnection().stopRecording(this); } Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class); intent.putParcelableArrayListExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, new ArrayList<>(Arrays.asList(trackIds))); diff --git a/src/main/java/de/dennisguse/opentracks/MarkerListActivity.java b/src/main/java/de/dennisguse/opentracks/MarkerListActivity.java index 71d706074..2080610c7 100644 --- a/src/main/java/de/dennisguse/opentracks/MarkerListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/MarkerListActivity.java @@ -18,8 +18,6 @@ package de.dennisguse.opentracks; import android.app.SearchManager; import android.content.Intent; -import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.database.Cursor; import android.os.Bundle; import android.view.KeyEvent; @@ -42,16 +40,18 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.databinding.MarkerListBinding; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller; +import de.dennisguse.opentracks.services.TrackRecordingServiceStatus; +import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; +import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.util.ActivityUtils; import de.dennisguse.opentracks.util.IntentUtils; -import de.dennisguse.opentracks.util.PreferencesUtils; /** * Activity to show a list of markers in a track. * * @author Leif Hendrik Wilden */ -public class MarkerListActivity extends AbstractActivity implements DeleteMarkerCaller { +public class MarkerListActivity extends AbstractActivity implements DeleteMarkerCaller, TrackRecordingServiceStatus.Listener { public static final String EXTRA_TRACK_ID = "track_id"; @@ -59,25 +59,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker private ContentProviderUtils contentProviderUtils; - private SharedPreferences sharedPreferences; - private Track.Id recordingTrackId; private boolean recordingTrackPaused; - - private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_id_key, key)) { - recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, MarkerListActivity.this); - } - if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_paused_key, key)) { - recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, MarkerListActivity.this); - } - if (key != null) { - runOnUiThread(MarkerListActivity.this::invalidateOptionsMenu); - } - } - }; private Track track; private MarkerResourceCursorAdapter resourceCursorAdapter; @@ -85,6 +68,18 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker private final MarkerLoaderCallback loaderCallbacks = new MarkerLoaderCallback(); + private TrackRecordingServiceConnection trackRecordingServiceConnection; + + private final Runnable bindCallback = new Runnable() { + @Override + public void run() { + TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound(); + if (service != null) { + service.addListener(MarkerListActivity.this); + } + } + }; + // Callback when an item is selected in the contextual action mode private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() { @Override @@ -112,10 +107,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker super.onCreate(savedInstanceState); setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID); - recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this); contentProviderUtils = new ContentProviderUtils(this); - sharedPreferences = PreferencesUtils.getSharedPreferences(this); track = trackId != null ? contentProviderUtils.getTrack(trackId) : null; @@ -132,18 +125,20 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker viewBinding.markerList.setOnScrollListener(scrollVisibleViews); viewBinding.markerList.setAdapter(resourceCursorAdapter); ActivityUtils.configureListViewContextualMenu(viewBinding.markerList, contextualActionModeCallback); + + trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback); } @Override protected void onStart() { super.onStart(); - sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null); + trackRecordingServiceConnection.startConnection(this); } @Override protected void onResume() { super.onResume(); + trackRecordingServiceConnection.bind(this); this.invalidateOptionsMenu(); loadData(getIntent()); } @@ -151,7 +146,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker @Override protected void onStop() { super.onStop(); - sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + trackRecordingServiceConnection.unbind(this); } @Override @@ -161,7 +156,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker viewBinding = null; resourceCursorAdapter = null; contentProviderUtils = null; - sharedPreferences = null; } @Override @@ -350,4 +344,14 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker resourceCursorAdapter.swapCursor(null); } } + + @Override + public void onTrackRecordingPaused(boolean isPaused) { + recordingTrackPaused = isPaused; + } + + @Override + public void onTrackRecordingId(Track.Id trackId) { + recordingTrackId = trackId; + } } diff --git a/src/main/java/de/dennisguse/opentracks/Startup.java b/src/main/java/de/dennisguse/opentracks/Startup.java index 4f7c41f8b..8362f297f 100644 --- a/src/main/java/de/dennisguse/opentracks/Startup.java +++ b/src/main/java/de/dennisguse/opentracks/Startup.java @@ -24,10 +24,10 @@ public class Startup extends Application { public void onCreate() { super.onCreate(); - //Include version information into stack traces. + // Include version information into stack traces. Log.i(TAG, BuildConfig.APPLICATION_ID + "; BuildType: " + BuildConfig.BUILD_TYPE + "; VersionName: " + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_NAME_FULL + " VersionCode: " + BuildConfig.VERSION_CODE); - //Set default values of preferences on first start. + // Set default values of preferences on first start. SharedPreferences sharedPreferences = PreferencesUtils.resetPreferences(this, false); if (PreferencesUtils.getString(sharedPreferences, this, R.string.stats_units_key, "").equals("")) { String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial : R.string.stats_units_metric); @@ -36,13 +36,7 @@ public class Startup extends Application { ActivityUtils.applyNightMode(sharedPreferences, this); - //TODO Workaround to reset recordingTrackId on app startup as the TrackRecordingService (likely) crashed. - if (PreferencesUtils.isRecording(sharedPreferences, this)) { - Log.e(TAG, "Reset recordingTrackId; likely the TrackRecordingService crashed."); - PreferencesUtils.setLong(sharedPreferences, this, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); - } - - //In debug builds: show thread and VM warnings. + // In debug builds: show thread and VM warnings. if (BuildConfig.DEBUG) { Log.d(TAG, "Enabling strict mode"); StrictMode.enableDefaults(); diff --git a/src/main/java/de/dennisguse/opentracks/TrackController.java b/src/main/java/de/dennisguse/opentracks/TrackController.java index 8c50b71a1..01842abde 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackController.java +++ b/src/main/java/de/dennisguse/opentracks/TrackController.java @@ -129,7 +129,7 @@ public class TrackController implements View.OnTouchListener { buttonDelay = () -> { view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); view.performClick(); - callback.recordStart(); //TODO Should be recordPause(); + callback.recordPause(); transition.resetTransition(); @@ -239,7 +239,7 @@ public class TrackController implements View.OnTouchListener { public interface Callback { void recordStart(); - + void recordPause(); void recordStop(); } } diff --git a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java index 96e1a3789..3c06a0eed 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java @@ -52,6 +52,7 @@ import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TracksColumns; import de.dennisguse.opentracks.databinding.TrackListBinding; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment; +import de.dennisguse.opentracks.services.TrackRecordingServiceStatus; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.services.handlers.GpsStatusValue; @@ -69,7 +70,7 @@ import de.dennisguse.opentracks.util.TrackIconUtils; * * @author Leif Hendrik Wilden */ -public class TrackListActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackController.Callback { +public class TrackListActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackController.Callback, TrackRecordingServiceStatus.Listener { private static final String TAG = TrackListActivity.class.getSimpleName(); @@ -86,7 +87,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe // Preferences private boolean metricUnits = true; - private Track.Id recordingTrackId = null; + + private boolean recordingTrackPaused = true; // Callback when an item is selected in the contextual action mode private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() { @@ -105,29 +107,17 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe } }; - private boolean recordingTrackPaused; - private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) { metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, TrackListActivity.this); } - if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) { - recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, TrackListActivity.this); - if (key != null && PreferencesUtils.isRecording(recordingTrackId)) { - trackRecordingServiceConnection.startAndBind(TrackListActivity.this); - } - } - if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) { - recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, TrackListActivity.this); - } if (key != null) { runOnUiThread(() -> { TrackListActivity.this.invalidateOptionsMenu(); loaderCallbacks.restart(); - boolean isRecording = PreferencesUtils.isRecording(recordingTrackId); - trackController.update(isRecording, recordingTrackPaused); + trackController.update(isRecording(), recordingTrackPaused); }); } } @@ -141,10 +131,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe private final Runnable bindChangedCallback = new Runnable() { @Override public void run() { - boolean isRecording = PreferencesUtils.isRecording(recordingTrackId); - // After binding changes (e.g., becomes available), update the total time in trackController. - runOnUiThread(() -> trackController.update(isRecording, recordingTrackPaused)); + runOnUiThread(() -> trackController.update(isRecording(), recordingTrackPaused)); TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound(); if (service == null) { @@ -155,11 +143,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe // Get GPS status and listen GPS status changes. gpsStatusValue = service.getGpsStatus(); - updateGpsMenuItem(true, isRecording); - service.addListener(newStatus -> { - gpsStatusValue = newStatus; - updateGpsMenuItem(true, isRecording); - }); + updateGpsMenuItem(true, isRecording()); + service.addListener(TrackListActivity.this); if (isGpsStarted()) { return; @@ -167,7 +152,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe service.startGps(); gpsStatusValue = GpsStatusValue.GPS_ENABLED; - updateGpsMenuItem(true, isRecording); + updateGpsMenuItem(true, isRecording()); } }; @@ -181,8 +166,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe gpsStatusValue = GpsStatusValue.GPS_NONE; - recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this); - sharedPreferences = PreferencesUtils.getSharedPreferences(this); trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback); @@ -197,7 +180,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView); viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> { - if (trackId == recordingTrackId.getId()) { + if (isRecording() && trackId == recordingTrackId.getId()) { // Is recording -> open record activity. Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class) .putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId)); @@ -281,7 +264,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe // Update UI this.invalidateOptionsMenu(); LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks); - trackController.onResume(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused); + trackController.onResume(isRecording(), recordingTrackPaused); } @Override @@ -322,9 +305,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe @Override public boolean onPrepareOptionsMenu(Menu menu) { - boolean isGpsStarted = isGpsStarted(); - boolean isRecording = PreferencesUtils.isRecording(recordingTrackId); - updateMenuItems(isGpsStarted, isRecording); + updateMenuItems(isGpsStarted(), isRecording()); SearchView searchView = (SearchView) searchMenuItem.getActionView(); searchView.setQuery("", false); @@ -572,7 +553,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe @Override public void recordStart() { - if (!PreferencesUtils.isRecording(recordingTrackId)) { + if (recordingTrackId == null) { // Not recording -> Recording updateMenuItems(false, true); Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class); @@ -582,17 +563,45 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe updateMenuItems(false, true); trackRecordingServiceConnection.resumeTrack(); trackController.update(true, false); - } else { - // Recording -> Paused - updateMenuItems(false, true); - trackRecordingServiceConnection.pauseTrack(); - trackController.update(true, true); } } + @Override + public void recordPause() { + updateMenuItems(false, true); + trackRecordingServiceConnection.pauseTrack(); + trackController.update(true, true); + } + @Override public void recordStop() { updateMenuItems(false, false); - trackRecordingServiceConnection.stopRecording(TrackListActivity.this, true); + trackRecordingServiceConnection.stopRecording(TrackListActivity.this); + } + + private boolean isRecording() { + return recordingTrackId != null; + } + + @Override + public void onGpsStatus(GpsStatusValue newStatus) { + gpsStatusValue = newStatus; + updateGpsMenuItem(true, isRecording()); + } + + @Override + public void onTrackRecordingPaused(boolean isPaused) { + if (recordingTrackPaused != isPaused) { + trackController.update(isRecording(), isPaused); + } + recordingTrackPaused = isPaused; + } + + @Override + public void onTrackRecordingId(Track.Id trackId) { + if (!isRecording() && trackId != null) { + trackController.update(true, recordingTrackPaused); + } + recordingTrackId = trackId; } } diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java index f5f7f4b88..7e1cc1141 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java @@ -40,11 +40,12 @@ import de.dennisguse.opentracks.fragments.ChartFragment; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.fragments.IntervalsFragment; import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment; +import de.dennisguse.opentracks.services.TrackRecordingServiceStatus; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; +import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.settings.SettingsActivity; import de.dennisguse.opentracks.util.IntentDashboardUtils; import de.dennisguse.opentracks.util.IntentUtils; -import de.dennisguse.opentracks.util.PreferencesUtils; /** * An activity to show the track detail, record a new track or resumes an existing one. @@ -52,7 +53,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils; * @author Leif Hendrik Wilden * @author Rodrigo Damazio */ -public class TrackRecordedActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface { +public class TrackRecordedActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface, TrackRecordingServiceStatus.Listener { private static final String TAG = TrackRecordedActivity.class.getSimpleName(); @@ -71,6 +72,18 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi private Track.Id trackId; + private TrackRecordingServiceConnection trackRecordingServiceConnection; + + private final Runnable bindCallback = new Runnable() { + @Override + public void run() { + TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound(); + if (service != null) { + service.addListener(TrackRecordedActivity.this); + } + } + }; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -88,6 +101,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY)); } + trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback); + postponeEnterTransition(); } @@ -107,11 +122,14 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi if (trackId != null) { trackDataHub.loadTrack(trackId); } + + trackRecordingServiceConnection.bind(this); } @Override protected void onStop() { super.onStop(); + trackRecordingServiceConnection.unbind(this); trackDataHub.stop(); } @@ -143,7 +161,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi @Override public boolean onPrepareOptionsMenu(Menu menu) { menu.findItem(R.id.track_detail_markers).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - menu.findItem(R.id.track_detail_resume_track).setVisible(!PreferencesUtils.isRecording(PreferencesUtils.getSharedPreferences(this), this)); + menu.findItem(R.id.track_detail_resume_track).setVisible(recordingTrackId == null); Track track = contentProviderUtils.getTrack(trackId); setTitle(track != null ? track.getName() : ""); return super.onPrepareOptionsMenu(menu); @@ -285,4 +303,9 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi ViewCompat.setTransitionName(viewIcon, TrackRecordedActivity.VIEW_TRACK_ICON); startPostponedEnterTransition(); } + + @Override + public void onTrackRecordingId(Track.Id trackId) { + recordingTrackId = trackId; + } } \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java index f7e8b3651..c06769276 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java @@ -26,6 +26,7 @@ import de.dennisguse.opentracks.fragments.ChartFragment; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; import de.dennisguse.opentracks.fragments.IntervalsFragment; import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment; +import de.dennisguse.opentracks.services.TrackRecordingServiceStatus; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.settings.SettingsActivity; @@ -43,7 +44,7 @@ import de.dennisguse.opentracks.util.TrackUtils; */ //NOTE: This activity does NOT react to preference changes of R.string.recording_track_id_key. //This mode of communication should be removed anyhow. -public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface, TrackController.Callback { +public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface, TrackController.Callback, TrackRecordingServiceStatus.Listener { public static final String EXTRA_TRACK_ID = "track_id"; @@ -62,8 +63,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc // Initialized from Intent; if a new track recording is started, a new TrackId will be provided by TrackRecordingService private Track.Id trackId; - - // Preferences private boolean recordingTrackPaused; private final Runnable bindChangedCallback = new Runnable() { @@ -78,7 +77,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc return; } if (!service.isRecording()) { - // Starts or resumes a track. if (trackId == null) { // trackId isn't initialized -> leads a new recording. trackId = service.startNewTrack(); @@ -92,31 +90,23 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc trackController.update(true, false); trackController.onResume(true, recordingTrackPaused); } + service.addListener(TrackRecordingActivity.this); } }; private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_paused_key, key)) { - recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, TrackRecordingActivity.this); - setLockscreenPolicy(); - setScreenOnPolicy(); - } - if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) { setLockscreenPolicy(); } - if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) { setScreenOnPolicy(); } if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_fullscreen_while_recording_key, key)) { setFullscreenPolicy(); } - if (key == null) return; - runOnUiThread(() -> { TrackRecordingActivity.this.invalidateOptionsMenu(); trackController.update(true, recordingTrackPaused); @@ -145,8 +135,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc } } - recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this); - trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback); trackDataHub = new TrackDataHub(this); @@ -345,22 +333,21 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc @Override public void recordStart() { - if (recordingTrackPaused) { - // Paused -> Resume - updateMenuItems(false); - trackRecordingServiceConnection.resumeTrack(); - trackController.update(true, false); - } else { - // Recording -> Paused - updateMenuItems(true); - trackRecordingServiceConnection.pauseTrack(); - trackController.update(true, true); - } + updateMenuItems(false); + trackRecordingServiceConnection.resumeTrack(); + trackController.update(true, false); + } + + @Override + public void recordPause() { + updateMenuItems(true); + trackRecordingServiceConnection.pauseTrack(); + trackController.update(true, true); } @Override public void recordStop() { - trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this, true); + trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this); Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackRecordedActivity.class) .putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId); startActivity(newIntent); @@ -412,4 +399,22 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc } } } + + @Override + public void onTrackRecordingPaused(boolean isPaused) { + if (recordingTrackPaused != isPaused) { + trackController.update(true, isPaused); + } + recordingTrackPaused = isPaused; + setLockscreenPolicy(); + setScreenOnPolicy(); + } + + @Override + public void onTrackRecordingId(Track.Id newTrackId) { + if (newTrackId != null && !newTrackId.equals(trackId)) { + trackId = newTrackId; + trackController.update(true, recordingTrackPaused); + } + } } diff --git a/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java b/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java index 072c31e0b..6dbec0f2c 100644 --- a/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java +++ b/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java @@ -18,7 +18,6 @@ package de.dennisguse.opentracks.content; import android.content.ContentResolver; import android.content.Context; -import android.content.SharedPreferences; import android.database.ContentObserver; import android.database.Cursor; import android.os.Handler; @@ -31,7 +30,6 @@ import androidx.annotation.VisibleForTesting; import java.util.Collections; import java.util.Set; -import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.content.data.Marker; import de.dennisguse.opentracks.content.data.MarkerColumns; import de.dennisguse.opentracks.content.data.Track; @@ -40,7 +38,9 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns; import de.dennisguse.opentracks.content.data.TracksColumns; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.TrackPointIterator; -import de.dennisguse.opentracks.util.PreferencesUtils; +import de.dennisguse.opentracks.services.TrackRecordingServiceStatus; +import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; +import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; /** * Track data hub. @@ -50,7 +50,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils; * * @author Rodrigo Damazio */ -public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeListener { +public class TrackDataHub implements TrackRecordingServiceStatus.Listener { /** * Target number of track points displayed by the diagrams (recommended). @@ -71,12 +71,12 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL private final ContentProviderUtils contentProviderUtils; private final int targetNumPoints; - private SharedPreferences sharedPreferences; - private boolean started; private HandlerThread handlerThread; private Handler handler; + private TrackRecordingServiceConnection trackRecordingServiceConnection; + // Preference values private Track.Id selectedTrackId; private Track.Id recordingTrackId; @@ -92,6 +92,13 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL private ContentObserver markersTableObserver; private ContentObserver trackPointsTableObserver; + private final Runnable bindCallback = () -> { + TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound(); + if (service != null) { + service.addListener(TrackDataHub.this); + } + }; + public TrackDataHub(Context context) { this(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACKPOINTS); } @@ -141,10 +148,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL }; contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver); - - sharedPreferences = PreferencesUtils.getSharedPreferences(context); - sharedPreferences.registerOnSharedPreferenceChangeListener(this); - onSharedPreferenceChanged(sharedPreferences, null); + trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback); + trackRecordingServiceConnection.bind(context); handler.post(() -> { if (started) { @@ -159,9 +164,6 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL return; } - sharedPreferences.unregisterOnSharedPreferenceChangeListener(this); - sharedPreferences = null; - started = false; //Unregister listeners @@ -175,6 +177,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL handlerThread = null; } handler = null; + + trackRecordingServiceConnection.unbind(context); } public void loadTrack(final @NonNull Track.Id trackId) { @@ -215,7 +219,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL * Returns true if the selected track is recording. */ public boolean isSelectedTrackRecording() { - return selectedTrackId != null && selectedTrackId.equals(recordingTrackId) && PreferencesUtils.isRecording(recordingTrackId); + return selectedTrackId != null && selectedTrackId.equals(recordingTrackId); } /** @@ -225,18 +229,6 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL return selectedTrackId != null && selectedTrackId.equals(recordingTrackId) && recordingTrackPaused; } - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) { - handler.post(() -> { - if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) { - recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, context); - } - if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) { - recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, context); - } - }); - } - /** * Loads data for all listeners. To be run in the {@link #handler} thread. */ @@ -432,4 +424,14 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL firstSeenTrackPointId = null; lastSeenTrackPointId = null; } + + @Override + public void onTrackRecordingPaused(boolean isPaused) { + recordingTrackPaused = isPaused; + } + + @Override + public void onTrackRecordingId(Track.Id trackId) { + recordingTrackId = trackId; + } } \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java index 7df2d9198..93984b982 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java @@ -362,9 +362,9 @@ public class ContentProviderUtils { } /** - * @return -1 if not able to get the next marker number. + * @return null if not able to get the next marker number. */ - public int getNextMarkerNumber(@NonNull Track.Id trackId) { + public Integer getNextMarkerNumber(@NonNull Track.Id trackId) { String[] projection = {MarkerColumns._ID}; String selection = MarkerColumns.TRACKID + "=?"; String[] selectionArgs = new String[]{Long.toString(trackId.getId())}; @@ -373,7 +373,7 @@ public class ContentProviderUtils { return cursor.getCount(); } } - return -1; + return null; } public Marker getMarker(@NonNull Marker.Id markerId) { diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java index 39725f0ce..0290d4300 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java @@ -40,7 +40,6 @@ import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter; import de.dennisguse.opentracks.util.FileUtils; -import de.dennisguse.opentracks.util.PreferencesUtils; /** * Imports a KMZ file. @@ -79,7 +78,6 @@ public class KmzTrackImporter implements TrackImporter { trackIdsWithImages.add(trackId); deleteOrphanImages(context, trackId); } else { - cleanImport(context, trackId); return new ArrayList<>(); } } @@ -230,18 +228,6 @@ public class KmzTrackImporter implements TrackImporter { } } - /** - * Cleans up import. - * - * @param trackId the trackId - */ - private void cleanImport(Context context, Track.Id trackId) { - if (PreferencesUtils.isRecording(trackId)) { - ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); - contentProviderUtils.deleteTrack(context, trackId); - } - } - private List parseKml(ZipInputStream zipInputStream) { KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context); diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index 3fe35e102..144c00f6e 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -37,8 +37,6 @@ import androidx.core.app.TaskStackBuilder; import java.time.Duration; import java.time.Instant; -import java.util.ArrayList; -import java.util.List; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.TrackListActivity; @@ -48,7 +46,6 @@ import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.CustomContentProvider; -import de.dennisguse.opentracks.content.provider.TrackPointIterator; import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.io.file.exporter.ExportServiceResultReceiver; import de.dennisguse.opentracks.services.handlers.GpsStatusValue; @@ -83,23 +80,13 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand private TrackRecordingServiceNotificationManager notificationManager; private SharedPreferences sharedPreferences; - private Track.Id recordingTrackId; - private boolean recordingTrackPaused; + private int recordingDistanceInterval; private int maxRecordingDistance; private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { Context context = TrackRecordingService.this; - if (PreferencesUtils.isKey(TrackRecordingService.this, R.string.recording_track_id_key, key)) { - // Only through the TrackRecordingService can one stop a recording and set the recordingTrackId to -1L. - if (PreferencesUtils.isRecording(sharedPreferences, TrackRecordingService.this)) { - recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, TrackRecordingService.this); - } - } - if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) { - recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, context); - } if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) { boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context); voiceExecutor.setMetricUnits(metricUnits); @@ -132,7 +119,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand private HandlerServer handlerServer; - private List listeners = new ArrayList<>(); + private final TrackRecordingServiceStatus serviceStatus = new TrackRecordingServiceStatus(); @Override public void onCreate() { @@ -145,14 +132,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand notificationManager = new TrackRecordingServiceNotificationManager(this); - // onSharedPreferenceChanged might not set recordingTrackId. - recordingTrackId = null; - sharedPreferences = PreferencesUtils.getSharedPreferences(this); sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null); - - restartTrackAfterServiceRestart(); } @Override @@ -167,13 +149,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand @Override public void onDestroy() { - if (listeners != null) { - for (TrackRecordingServiceCallback listener : listeners) { - listener.onGpsStatusChange(GpsStatusValue.GPS_NONE); - } - listeners.clear(); - listeners = null; - } + serviceStatus.onStop(); handlerServer.stop(this); handlerServer = null; @@ -212,15 +188,11 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand } public boolean isRecording() { - return PreferencesUtils.isRecording(recordingTrackId); + return serviceStatus.isRecording(); } public boolean isPaused() { - return recordingTrackPaused; - } - - public Track.Id getRecordingTrackId() { - return recordingTrackId; + return serviceStatus.getRecordingTrackPaused(); } public TrackStatistics getTrackStatistics() { @@ -250,14 +222,14 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand } if (name == null) { - int nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(recordingTrackId); - if (nextMarkerNumber == -1) { + Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(serviceStatus.getRecordingTrackId()); + if (nextMarkerNumber == null) { nextMarkerNumber = 1; } name = getString(R.string.marker_name_format, nextMarkerNumber + 1); } - TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(recordingTrackId); + TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(serviceStatus.getRecordingTrackId()); if (trackPoint == null) { Log.i(TAG, "Could not create a marker as trackPoint is unknown."); return null; @@ -273,7 +245,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand long duration = stats.getTotalTime().toMillis(); // Insert marker - Marker marker = new Marker(name, description, category, icon, recordingTrackId, length, duration, trackPoint, photoUrl); + Marker marker = new Marker(name, description, category, icon, serviceStatus.getRecordingTrackId(), length, duration, trackPoint, photoUrl); Uri uri = contentProviderUtils.insertMarker(marker); return new Marker.Id(ContentUris.parseId(uri)); } @@ -294,8 +266,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand Uri uri = contentProviderUtils.insertTrack(track); Track.Id trackId = new Track.Id(ContentUris.parseId(uri)); - // Update shared preferences - updateRecordingState(trackId, false); + // Set recording status + serviceStatus.onChange(trackId, false); // Update database track.setId(trackId); @@ -336,48 +308,23 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand insertTrackPoint(track, TrackPoint.createSegmentStartManual()); - // Update shared preferences. - updateRecordingState(trackId, false); + // Set recording status + serviceStatus.onChange(trackId, false); startRecording(); } - /** - * Try to restart the previous recording track in case the service has been restarted by the system, which can sometimes happen. - */ - private void restartTrackAfterServiceRestart() { - Track track = contentProviderUtils.getTrack(recordingTrackId); - if (track == null) { - if (isRecording()) { - Log.w(TAG, "track is null, but recordingTrackId not -1L. " + recordingTrackId.getId()); - updateRecordingState(null, true); - } - showNotification(false); - return; - } - - Log.d(TAG, "Restarting track: " + track.getId()); - - trackStatisticsUpdater = new TrackStatisticsUpdater(); - - try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) { - trackStatisticsUpdater.addTrackPoint(trackPointIterator, recordingDistanceInterval); - } catch (RuntimeException e) { - Log.e(TAG, "RuntimeException", e); - } - startRecording(); - } - void resumeCurrentTrack() { if (!isRecording() || !isPaused()) { Log.d(TAG, "Ignore resumeCurrentTrack. Not recording or not paused."); return; } - updateRecordingState(recordingTrackId, false); + // Set recording status + serviceStatus.onChange(false); // Update database - Track track = contentProviderUtils.getTrack(recordingTrackId); + Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId()); if (track != null) { insertTrackPoint(track, TrackPoint.createSegmentStartManual()); } @@ -417,17 +364,18 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand showNotification(true); } - void endCurrentTrack() { + Track.Id endCurrentTrack() { if (!isRecording()) { Log.d(TAG, "Ignore endCurrentTrack. Not recording."); - return; + return null; } - // Need to remember the recordingTrackId before setting it to -1L + // Need to remember the recordingTrackId before setting it to null boolean wasPause = isPaused(); - Track.Id trackId = recordingTrackId; + Track.Id trackId = serviceStatus.getRecordingTrackId(); - updateRecordingState(null, true); + // Set recording status + serviceStatus.onChange(null, true); if (!wasPause) { // Update database @@ -445,6 +393,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand ExportUtils.postWorkoutExport(this, track, new ExportServiceResultReceiver(new Handler(), this)); endRecording(true); + + return trackId; } void pauseCurrentTrack() { @@ -453,10 +403,11 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand return; } - updateRecordingState(recordingTrackId, true); + // Set recording status + serviceStatus.onChange(true); // Update database - Track track = contentProviderUtils.getTrack(recordingTrackId); + Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId()); if (track != null) { if (lastTrackPoint != null) { insertTrackPointIfNewer(track, lastTrackPoint); @@ -528,21 +479,6 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand return lastTrackPoint != null; } - /** - * Updates the recording states. - * This will inform subscribed {@link OnSharedPreferenceChangeListener}. - * - * @param trackId the recording track id - * @param paused true if the recording is paused - */ - private void updateRecordingState(Track.Id trackId, boolean paused) { - recordingTrackId = trackId; - long currentTrackId = trackId != null ? trackId.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; - PreferencesUtils.setLong(sharedPreferences, this, R.string.recording_track_id_key, currentTrackId); - recordingTrackPaused = paused; - PreferencesUtils.setBoolean(sharedPreferences, this, R.string.recording_track_paused_key, recordingTrackPaused); - } - @Override public void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy) { if (!isRecording() || isPaused()) { @@ -550,7 +486,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand return; } - Track track = contentProviderUtils.getTrack(recordingTrackId); + Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId()); if (track == null) { Log.w(TAG, "Ignore newTrackPoint. No track."); return; @@ -623,16 +559,12 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand @Override public void newGpsStatus(GpsStatusValue gpsStatusValue) { - if (listeners != null) { - notificationManager.updateContent(getString(gpsStatusValue.message)); - for (TrackRecordingServiceCallback listener : listeners) { - listener.onGpsStatusChange(gpsStatusValue); - } - } + notificationManager.updateContent(getString(gpsStatusValue.message)); + serviceStatus.onChange(gpsStatusValue); } - public void addListener(TrackRecordingServiceCallback listener) { - listeners.add(listener); + public void addListener(@NonNull TrackRecordingServiceStatus.Listener listener) { + serviceStatus.addListener(listener); } /** @@ -724,7 +656,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand private void showNotification(boolean isGpsStarted) { if (isRecording()) { Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class) - .putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, recordingTrackId); + .putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, serviceStatus.getRecordingTrackId()); PendingIntent pendingIntent = TaskStackBuilder.create(this) .addParentStack(TrackRecordingActivity.class) .addNextIntent(intent) diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceBinder.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceBinder.java index 3cc7be682..03f977241 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceBinder.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceBinder.java @@ -1,5 +1,6 @@ package de.dennisguse.opentracks.services; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import java.time.Duration; @@ -28,7 +29,7 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco } @Override - public void addListener(TrackRecordingServiceCallback listener) { + public void addListener(@NonNull TrackRecordingServiceStatus.Listener listener) { trackRecordingService.addListener(listener); } @@ -67,8 +68,8 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco } @Override - public void endCurrentTrack() { - trackRecordingService.endCurrentTrack(); + public Track.Id endCurrentTrack() { + return trackRecordingService.endCurrentTrack(); } @Override @@ -81,11 +82,6 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco return trackRecordingService.isPaused(); } - @Override - public Track.Id getRecordingTrackId() { - return trackRecordingService.getRecordingTrackId(); - } - @Override public Duration getTotalTime() { return trackRecordingService.getTotalTime(); diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceCallback.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceCallback.java deleted file mode 100644 index bef363c31..000000000 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceCallback.java +++ /dev/null @@ -1,10 +0,0 @@ -package de.dennisguse.opentracks.services; - -import de.dennisguse.opentracks.services.handlers.GpsStatusValue; - -/** - * Interface all activities have to implements to receive information from the service. - */ -public interface TrackRecordingServiceCallback { - void onGpsStatusChange(GpsStatusValue newStatus); -} diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceConnection.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceConnection.java index bfa67c665..21b2e4c06 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceConnection.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceConnection.java @@ -20,7 +20,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.content.SharedPreferences; import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.RemoteException; @@ -31,11 +30,7 @@ import androidx.annotation.NonNull; import de.dennisguse.opentracks.BuildConfig; import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.TrackEditActivity; import de.dennisguse.opentracks.content.data.Marker; -import de.dennisguse.opentracks.content.data.Track; -import de.dennisguse.opentracks.util.IntentUtils; -import de.dennisguse.opentracks.util.PreferencesUtils; /** * Wrapper for the track recording service. @@ -64,6 +59,13 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death this.callback = callback; } + public void bind(@NonNull Context context) { + if (trackRecordingService != null) { + return; + } + context.bindService(new Intent(context, TrackRecordingService.class), this, 0); + } + /** * Starts and binds the service. */ @@ -202,30 +204,12 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death return null; } - public void stopRecording(@NonNull Context context, boolean showEditor) { + public void stopRecording(@NonNull Context context) { TrackRecordingServiceInterface trackRecordingService = getServiceIfBound(); if (trackRecordingService == null) { Log.e(TAG, "TrackRecordingService not connected."); } else { - try { - if (showEditor) { - // Need to remember the recordingTrackId before calling endCurrentTrack() as endCurrentTrack() sets the value to -1L. - SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context); - Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, context); - trackRecordingService.endCurrentTrack(); - if (PreferencesUtils.isRecording(sharedPreferences, context)) { - Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class) - .putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId) - .putExtra(TrackEditActivity.EXTRA_NEW_TRACK, true); - context.startActivity(intent); - } - } else { - trackRecordingService.endCurrentTrack(); - } - } catch (Exception e) { - //TODO What exception are we catching here? Should be removed... - Log.e(TAG, "Unable to stop recording.", e); - } + trackRecordingService.endCurrentTrack(); } unbindAndStop(context); } diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceInterface.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceInterface.java index 99486c79c..84b04c477 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceInterface.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceInterface.java @@ -15,6 +15,7 @@ */ package de.dennisguse.opentracks.services; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import java.time.Duration; @@ -44,7 +45,12 @@ public interface TrackRecordingServiceInterface { void resumeTrack(Track.Id trackId); - void endCurrentTrack(); + /** + * End current track. + * + * @return the Track.Id of the track that is ended. + */ + Track.Id endCurrentTrack(); boolean isRecording(); @@ -53,11 +59,6 @@ public interface TrackRecordingServiceInterface { */ boolean isPaused(); - /** - * Gets the current recording track ID. Returns null if not recording. - */ - Track.Id getRecordingTrackId(); - /** * Gets the total time for the current recording track. Returns 0 if not recording. */ @@ -100,7 +101,7 @@ public interface TrackRecordingServiceInterface { @VisibleForTesting void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy); - void addListener(TrackRecordingServiceCallback listener); + void addListener(@NonNull TrackRecordingServiceStatus.Listener listener); GpsStatusValue getGpsStatus(); } diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceStatus.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceStatus.java new file mode 100644 index 000000000..c38cb6f80 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceStatus.java @@ -0,0 +1,79 @@ +package de.dennisguse.opentracks.services; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +import de.dennisguse.opentracks.content.data.Track; +import de.dennisguse.opentracks.services.handlers.GpsStatusValue; + +/** + * This class handles the status of the recording service. + * Also offer an interface through which to know the recording Track.Id, pause status and GPS status. + */ +public class TrackRecordingServiceStatus { + + private Track.Id recordingTrackId; + private boolean recordingTrackPaused; + + private final List listeners = new ArrayList<>(); + + public void addListener(@NonNull Listener listener) { + if (this.listeners.contains(listener)) { + return; + } + this.listeners.add(listener); + listener.onTrackRecordingId(recordingTrackId); + listener.onTrackRecordingPaused(recordingTrackPaused); + } + + public boolean getRecordingTrackPaused() { + return this.recordingTrackPaused; + } + + public Track.Id getRecordingTrackId() { + return this.recordingTrackId; + } + + public void onStop() { + for (Listener listener : listeners) { + listener.onGpsStatus(GpsStatusValue.GPS_NONE); + listener.onTrackRecordingId(null); + listener.onTrackRecordingPaused(true); + } + listeners.clear(); + } + + public boolean isRecording() { + return recordingTrackId != null; + } + + void onChange(Track.Id trackId, boolean paused) { + recordingTrackId = trackId; + recordingTrackPaused = paused; + for (Listener listener : listeners) { + listener.onTrackRecordingId(recordingTrackId); + listener.onTrackRecordingPaused(recordingTrackPaused); + } + } + + void onChange(boolean paused) { + recordingTrackPaused = paused; + for (Listener listener : listeners) { + listener.onTrackRecordingPaused(recordingTrackPaused); + } + } + + void onChange(GpsStatusValue statusValue) { + for (Listener listener : listeners) { + listener.onGpsStatus(statusValue); + } + } + + public interface Listener { + default void onGpsStatus(GpsStatusValue newValue) {} + default void onTrackRecordingPaused(boolean paused) {} + default void onTrackRecordingId(Track.Id trackId) {} + } +} diff --git a/src/main/java/de/dennisguse/opentracks/services/tasks/AnnouncementPeriodicTask.java b/src/main/java/de/dennisguse/opentracks/services/tasks/AnnouncementPeriodicTask.java index be13f0888..b913d4535 100644 --- a/src/main/java/de/dennisguse/opentracks/services/tasks/AnnouncementPeriodicTask.java +++ b/src/main/java/de/dennisguse/opentracks/services/tasks/AnnouncementPeriodicTask.java @@ -31,6 +31,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.TrackPointIterator; import de.dennisguse.opentracks.services.TrackRecordingService; +import de.dennisguse.opentracks.services.TrackRecordingServiceStatus; import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.util.AnnouncementUtils; import de.dennisguse.opentracks.util.PreferencesUtils; @@ -57,6 +58,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask { private final ContentProviderUtils contentProviderUtils; + private Track.Id recordingTrackId; + private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { @Override public void onAudioFocusChange(int focusChange) { @@ -139,6 +142,12 @@ public class AnnouncementPeriodicTask implements PeriodicTask { Log.e(TAG, "TrackRecordingService is null."); return; } + trackRecordingService.addListener(new TrackRecordingServiceStatus.Listener() { + @Override + public void onTrackRecordingId(Track.Id trackId) { + recordingTrackId = trackId; + } + }); announce(trackRecordingService.getTrackStatistics()); } @@ -171,7 +180,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask { return; } - Track track = contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(sharedPreferences, context)); + Track track = contentProviderUtils.getTrack(recordingTrackId); String category = track != null ? track.getCategory() : ""; //TODO Querying all TrackPoints all the time is inefficient; use TrackDataHub or something else. diff --git a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java index aab08c047..1a7586736 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java +++ b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java @@ -20,8 +20,12 @@ import java.util.Locale; import de.dennisguse.opentracks.AbstractActivity; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; import de.dennisguse.opentracks.io.file.TrackFileFormat; +import de.dennisguse.opentracks.services.TrackRecordingServiceStatus; +import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; +import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference; import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference; import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference; @@ -89,14 +93,11 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, prefsFragment).commit(); } - public static class PrefsFragment extends PreferenceFragmentCompat { + public static class PrefsFragment extends PreferenceFragmentCompat implements TrackRecordingServiceStatus.Listener { private SharedPreferences sharedPreferences; private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> { - if (PreferencesUtils.isKey(getActivity(), R.string.recording_track_id_key, key)) { - getActivity().runOnUiThread(this::updateReset); - } if (PreferencesUtils.isKey(getActivity(), R.string.stats_units_key, key)) { getActivity().runOnUiThread(this::updateUnits); } @@ -105,6 +106,19 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity } }; + private TrackRecordingServiceConnection trackRecordingServiceConnection; + private boolean isRecording; + + private final Runnable bindServiceCallback = new Runnable() { + @Override + public void run() { + TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound(); + if (service != null) { + service.addListener(PrefsFragment.this); + } + } + }; + // Used to forward update from ChooseActivityTypeDialogFragment; TODO Could be replaced with LiveData. private ActivityTypePreference.ActivityPreferenceDialog activityPreferenceDialog; @@ -112,6 +126,8 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { sharedPreferences = PreferencesUtils.getSharedPreferences(getContext()); + trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindServiceCallback); + try { setPreferencesFromResource(R.xml.settings, rootKey); } catch (ClassCastException e) { @@ -137,13 +153,14 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity public void onResume() { super.onResume(); + trackRecordingServiceConnection.bind(getContext()); + sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null); updateUnits(); - updateReset(); + updatePrefsDependOnRecording(); updateBluetooth(); - updatePostWorkoutExport(); } @@ -186,6 +203,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity @Override public void onDestroy() { super.onDestroy(); + trackRecordingServiceConnection.unbind(getContext()); sharedPreferences = null; } @@ -195,11 +213,18 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity } } - private void updateReset() { - final boolean isRecording = PreferencesUtils.isRecording(sharedPreferences, getActivity()); + private void updatePrefsDependOnRecording() { Preference resetPreference = findPreference(getString(R.string.settings_reset_key)); + Preference importPreference = findPreference(getString(R.string.settings_import)); + Preference exportPreference = findPreference(getString(R.string.settings_export)); + resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : ""); + importPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : ""); + exportPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : ""); + resetPreference.setEnabled(!isRecording); + importPreference.setEnabled(!isRecording); + exportPreference.setEnabled(!isRecording); } private void updateBluetooth() { @@ -266,6 +291,14 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity return false; }); } + + @Override + public void onTrackRecordingId(Track.Id trackId) { + if (trackId != null && !isRecording && isAdded()) { + isRecording = true; + updatePrefsDependOnRecording(); + } + } } } diff --git a/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java index 51bf23509..d7ab479fd 100644 --- a/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java @@ -27,7 +27,6 @@ import androidx.documentfile.provider.DocumentFile; import androidx.preference.PreferenceManager; import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.io.file.TrackFileFormat; /** @@ -47,21 +46,6 @@ public class PreferencesUtils { return PreferenceManager.getDefaultSharedPreferences(context); } - @Deprecated - //NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly. - //NOTE: This was also used to recover from a service restart, but this data should not be exposed to the whole application. - public static final long RECORDING_TRACK_ID_DEFAULT = -1L; - - @Deprecated //Use the TrackRecordingService - public static Track.Id getRecordingTrackId(Context context, SharedPreferences sharedPreferences) { - return new Track.Id(sharedPreferences.getLong(getKey(context, R.string.recording_track_id_key), RECORDING_TRACK_ID_DEFAULT)); - } - - @Deprecated //Use the TrackRecordingService - public static Track.Id getRecordingTrackId(SharedPreferences sharedPreferences, Context context) { - return getRecordingTrackId(context, sharedPreferences); - } - public static String getDefaultActivity(SharedPreferences sharedPreferences, Context context) { return getString(sharedPreferences, context, R.string.default_activity_key, context.getString(R.string.default_activity_default)); } @@ -95,14 +79,6 @@ public class PreferencesUtils { return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue); } - //TODO Don't use; this function is only to be used TrackRecordingService and will be removed. - @VisibleForTesting - public static void setBoolean(SharedPreferences sharedPreferences, Context context, int keyId, boolean value) { - Editor editor = sharedPreferences.edit(); - editor.putBoolean(getKey(context, keyId), value); - editor.apply(); - } - private static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) { try { return sharedPreferences.getInt(getKey(context, keyId), defaultValue); @@ -119,22 +95,6 @@ public class PreferencesUtils { } } - @VisibleForTesting - public static void setInt(SharedPreferences sharedPreferences, Context context, int keyId, int value) { - Editor editor = sharedPreferences.edit(); - editor.putInt(getKey(context, keyId), value); - editor.apply(); - } - - //TODO Don't use; this function is only to be used TrackRecordingService and will be removed. - @Deprecated - @VisibleForTesting - public static void setLong(SharedPreferences sharedPreferences, Context context, int keyId, long value) { - Editor editor = sharedPreferences.edit(); - editor.putLong(getKey(context, keyId), value); - editor.apply(); - } - public static String getString(SharedPreferences sharedPreferences, Context context, int keyId, String defaultValue) { return sharedPreferences.getString(getKey(context, keyId), defaultValue); } @@ -161,22 +121,6 @@ public class PreferencesUtils { return currentStatsRate.equals(context.getString(R.string.stats_rate_speed)); } - @Deprecated //Use TrackRecordingService - public static boolean isRecordingTrackPaused(SharedPreferences sharedPreferences, Context context) { - return getBoolean(sharedPreferences, context, R.string.recording_track_paused_key, isRecordingTrackPausedDefault(context)); - } - - @Deprecated //Use TrackRecordingService - public static boolean isRecordingTrackPausedDefault(Context context) { - return context.getResources().getBoolean(R.bool.recording_track_paused_default); - } - - @Deprecated //Use TrackRecordingService - public static void defaultRecordingTrackPaused(SharedPreferences sharedPreferences, Context context) { - final boolean RECORDING_TRACK_PAUSED = context.getResources().getBoolean(R.bool.recording_track_paused_default); - setBoolean(sharedPreferences, context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED); - } - private static String getBluetoothSensorAddressNone(Context context) { return context.getString(R.string.sensor_type_value_none); } @@ -302,20 +246,6 @@ public class PreferencesUtils { return Integer.parseInt(value); } - @Deprecated //Use TrackRecordingService - public static boolean isRecording(SharedPreferences sharedPreferences, Context context) { - return isRecording(getRecordingTrackId(context, sharedPreferences)); - } - - @Deprecated - //TODO Method is very misleading: it only checks if the provided trackId not the default value (i.e., not recording). - public static boolean isRecording(Track.Id recordingTrackId) { - if (recordingTrackId == null) { - return false; - } - return recordingTrackId.getId() != RECORDING_TRACK_ID_DEFAULT; - } - public static SharedPreferences resetPreferences(Context context, boolean readAgain) { SharedPreferences sharedPreferences = getSharedPreferences(context); if (readAgain) { diff --git a/src/main/res/values/preferences_internal.xml b/src/main/res/values/preferences_internal.xml deleted file mode 100644 index 9653c35b7..000000000 --- a/src/main/res/values/preferences_internal.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - recordingTrackId - - recordingTrackPaused - true - \ No newline at end of file diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml index ad77abd22..ffaf84cb7 100644 --- a/src/main/res/values/settings.xml +++ b/src/main/res/values/settings.xml @@ -31,6 +31,9 @@ instantExportEnabled false + settingsImport + settingsExport + defaultActivity @string/activity_type_unknown diff --git a/src/main/res/xml/settings.xml b/src/main/res/xml/settings.xml index e792e4d08..0eaace250 100644 --- a/src/main/res/xml/settings.xml +++ b/src/main/res/xml/settings.xml @@ -148,12 +148,16 @@ limitations under the License. - + - +