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.
This commit is contained in:
Román Martínez
2021-03-23 20:08:25 +01:00
parent f3cf1a2571
commit e09b04308c
26 changed files with 426 additions and 466 deletions
@@ -606,7 +606,7 @@ public class CustomContentProviderUtilsTest {
contentProviderUtils.insertMarker(marker3); contentProviderUtils.insertMarker(marker3);
contentProviderUtils.insertMarker(marker4); contentProviderUtils.insertMarker(marker4);
assertEquals(4, contentProviderUtils.getNextMarkerNumber(trackId)); assertEquals(Integer.valueOf(4), contentProviderUtils.getNextMarkerNumber(trackId));
} }
/** /**
@@ -6,6 +6,7 @@ import android.content.SharedPreferences;
import android.os.Looper; import android.os.Looper;
import android.util.Log; import android.util.Log;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest; import androidx.test.filters.LargeTest;
import androidx.test.rule.GrantPermissionRule; import androidx.test.rule.GrantPermissionRule;
@@ -215,7 +216,9 @@ public class ExportImportTest {
@Test(expected = ImportAlreadyExistsException.class) @Test(expected = ImportAlreadyExistsException.class)
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() { public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() {
// given // 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); Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context); TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
@@ -285,7 +288,9 @@ public class ExportImportTest {
@Test(expected = ImportAlreadyExistsException.class) @Test(expected = ImportAlreadyExistsException.class)
public void gpx_duplicate_trackUUID() { public void gpx_duplicate_trackUUID() {
// given // 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); Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context); TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
@@ -38,6 +38,9 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; 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.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -151,31 +154,30 @@ public class TrackRecordingServiceTest {
// then // then
// Test if we start in no-recording mode by default. // Test if we start in no-recording mode by default.
assertFalse(service.isRecording()); assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
} }
@MediumTest @MediumTest
@Test @Test
public void testRecording_oldTracks() throws TimeoutException { public void testRecording_oldTracks() throws TimeoutException {
// given // given
createDummyTrack(trackId, false); createDummyTrack(trackId);
// when // when
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
// then // then
assertFalse(service.isRecording()); assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
} }
@MediumTest @MediumTest
@Test @Test
public void testRecording_serviceRestart_whileRecording() throws TimeoutException { public void testRecording_serviceRestart_whileRecording() throws TimeoutException {
// given // given
createDummyTrack(trackId, true); createDummyTrack(trackId);
//when //when
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
service.resumeTrack(trackId);
// then // then
assertTrue(service.isRecording()); assertTrue(service.isRecording());
@@ -233,7 +235,6 @@ public class TrackRecordingServiceTest {
// then // then
assertTrue(service.isRecording()); assertTrue(service.isRecording());
assertEquals(trackId, service.getRecordingTrackId());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(3, trackPoints.size()); assertEquals(3, trackPoints.size());
@@ -259,7 +260,6 @@ public class TrackRecordingServiceTest {
// then // then
assertTrue(service.isRecording()); assertTrue(service.isRecording());
assertEquals(trackId, service.getRecordingTrackId());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(4, trackPoints.size()); assertEquals(4, trackPoints.size());
@@ -285,7 +285,6 @@ public class TrackRecordingServiceTest {
// then // then
assertFalse(service.isRecording()); assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId); List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertEquals(2, trackPoints.size()); assertEquals(2, trackPoints.size());
@@ -293,41 +292,20 @@ public class TrackRecordingServiceTest {
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType()); 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 @MediumTest
@Test @Test
public void testStartNewTrack_alreadyRecording() throws TimeoutException { public void testStartNewTrack_alreadyRecording() throws TimeoutException {
// given // given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack(); Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording()); assertTrue(service.isRecording());
Track.Id trackId = service.getRecordingTrackId();
// when // when
Track.Id newTrackId = service.startNewTrack(); Track.Id newTrackId = service.startNewTrack();
// then // then
assertNotNull(trackId);
assertNull(newTrackId); assertNull(newTrackId);
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
assertEquals(trackId, service.getRecordingTrackId());
} }
@MediumTest @MediumTest
@@ -342,8 +320,7 @@ public class TrackRecordingServiceTest {
service.endCurrentTrack(); service.endCurrentTrack();
// then // then
assertFalse(PreferencesUtils.isRecording(sharedPreferences, context)); assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
} }
@MediumTest @MediumTest
@@ -365,10 +342,9 @@ public class TrackRecordingServiceTest {
public void testInsertWaypointMarker_validWaypoint() throws TimeoutException, InterruptedException { public void testInsertWaypointMarker_validWaypoint() throws TimeoutException, InterruptedException {
// given // given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack(); Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording()); assertTrue(service.isRecording());
newTrackPoint(service); newTrackPoint(service);
Track.Id trackId = service.getRecordingTrackId();
// when // when
Marker.Id markerId = service.insertMarker(null, null, null, null); Marker.Id markerId = service.insertMarker(null, null, null, null);
@@ -385,22 +361,23 @@ public class TrackRecordingServiceTest {
service.endCurrentTrack(); service.endCurrentTrack();
} }
private void addTrack(Track track, boolean isRecording) { private void addTrack(Track track) {
assertNotNull(track.getId()); assertNotNull(track.getId());
contentProviderUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId()); 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. // 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(); Track dummyTrack = new Track();
dummyTrack.setId(id); dummyTrack.setId(id);
dummyTrack.setName("Dummy Track"); dummyTrack.setName("Dummy Track");
TrackStatistics trackStatistics = new TrackStatistics(); TrackStatistics trackStatistics = new TrackStatistics();
Instant now = Instant.now();
trackStatistics.setStartTime(now.minusSeconds(5L));
trackStatistics.setStopTime(now.minusSeconds(1L));
dummyTrack.setTrackStatistics(trackStatistics); dummyTrack.setTrackStatistics(trackStatistics);
addTrack(dummyTrack, isRecording); addTrack(dummyTrack);
} }
private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException { private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException {
@@ -6,6 +6,7 @@ import android.content.SharedPreferences;
import android.location.Location; import android.location.Location;
import android.os.Looper; import android.os.Looper;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest; 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
@@ -99,28 +99,36 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRequiredAccuracy() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_voiceFrequencyByDistance() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_voiceFrequencyByTime() throws TimeoutException { 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(); fullRecordingSession();
} }
@@ -128,29 +136,36 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_maxRecordingDistance() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException { public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)); SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_distance_interval_key, minRecordingIntervalDefault); editor.putInt(context.getString(R.string.recording_distance_interval_key), Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
editor.commit();
fullRecordingSession(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRecordingDistance() throws TimeoutException { 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(); fullRecordingSession();
} }
@@ -171,22 +186,27 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRecordingInterval() throws TimeoutException { 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(); fullRecordingSession();
} }
@MediumTest @MediumTest
@Test @Test
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException { public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default)); SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault); editor.putInt(context.getString(R.string.recording_gps_accuracy_key), Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default)));
editor.commit();
fullRecordingSession(); fullRecordingSession();
} }
@@ -209,8 +229,6 @@ public class TrackRecordingServiceTestLooper {
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
assertNotNull(track); assertNotNull(track);
assertEquals(trackId, track.getId()); assertEquals(trackId, track.getId());
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
assertEquals(trackId, service.getRecordingTrackId());
// Insert a few points, markers and statistics. // Insert a few points, markers and statistics.
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
@@ -234,7 +252,6 @@ public class TrackRecordingServiceTestLooper {
// Stop the track. Validate if it has correct data. // Stop the track. Validate if it has correct data.
service.endCurrentTrack(); service.endCurrentTrack();
assertFalse(service.isRecording()); assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
track = contentProviderUtils.getTrack(trackId); track = contentProviderUtils.getTrack(trackId);
assertNotNull(track); assertNotNull(track);
assertEquals(trackId, track.getId()); assertEquals(trackId, track.getId());
@@ -26,7 +26,6 @@ import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/** /**
* An abstract class for the following common tasks across * 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; protected static final int GPS_REQUEST_CODE = 6;
private static final int DELETE_REQUEST_CODE = 3; private static final int DELETE_REQUEST_CODE = 3;
protected Track.Id recordingTrackId;
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == DELETE_REQUEST_CODE) { if (requestCode == DELETE_REQUEST_CODE) {
@@ -66,7 +67,6 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
public void onConfirmDeleteDone(Track.Id... trackIds) { public void onConfirmDeleteDone(Track.Id... trackIds) {
boolean stopRecording = false; boolean stopRecording = false;
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(PreferencesUtils.getSharedPreferences(this), this);
for (Track.Id trackId : trackIds) { for (Track.Id trackId : trackIds) {
if (trackId.equals(recordingTrackId)) { if (trackId.equals(recordingTrackId)) {
stopRecording = true; stopRecording = true;
@@ -75,7 +75,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
} }
if (stopRecording) { if (stopRecording) {
getTrackRecordingServiceConnection().stopRecording(this, false); getTrackRecordingServiceConnection().stopRecording(this);
} }
Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class); Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class);
intent.putParcelableArrayListExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, new ArrayList<>(Arrays.asList(trackIds))); intent.putParcelableArrayListExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, new ArrayList<>(Arrays.asList(trackIds)));
@@ -18,8 +18,6 @@ package de.dennisguse.opentracks;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.view.KeyEvent; 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.databinding.MarkerListBinding;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller; 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.ActivityUtils;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/** /**
* Activity to show a list of markers in a track. * Activity to show a list of markers in a track.
* *
* @author Leif Hendrik Wilden * @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"; public static final String EXTRA_TRACK_ID = "track_id";
@@ -59,25 +59,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private Track.Id recordingTrackId; private Track.Id recordingTrackId;
private boolean recordingTrackPaused; 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 Track track;
private MarkerResourceCursorAdapter resourceCursorAdapter; private MarkerResourceCursorAdapter resourceCursorAdapter;
@@ -85,6 +68,18 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private final MarkerLoaderCallback loaderCallbacks = new MarkerLoaderCallback(); 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 // Callback when an item is selected in the contextual action mode
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() { private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
@Override @Override
@@ -112,10 +107,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID); Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = new ContentProviderUtils(this); contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
track = trackId != null ? contentProviderUtils.getTrack(trackId) : null; track = trackId != null ? contentProviderUtils.getTrack(trackId) : null;
@@ -132,18 +125,20 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
viewBinding.markerList.setOnScrollListener(scrollVisibleViews); viewBinding.markerList.setOnScrollListener(scrollVisibleViews);
viewBinding.markerList.setAdapter(resourceCursorAdapter); viewBinding.markerList.setAdapter(resourceCursorAdapter);
ActivityUtils.configureListViewContextualMenu(viewBinding.markerList, contextualActionModeCallback); ActivityUtils.configureListViewContextualMenu(viewBinding.markerList, contextualActionModeCallback);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback);
} }
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); trackRecordingServiceConnection.startConnection(this);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
trackRecordingServiceConnection.bind(this);
this.invalidateOptionsMenu(); this.invalidateOptionsMenu();
loadData(getIntent()); loadData(getIntent());
} }
@@ -151,7 +146,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); trackRecordingServiceConnection.unbind(this);
} }
@Override @Override
@@ -161,7 +156,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
viewBinding = null; viewBinding = null;
resourceCursorAdapter = null; resourceCursorAdapter = null;
contentProviderUtils = null; contentProviderUtils = null;
sharedPreferences = null;
} }
@Override @Override
@@ -350,4 +344,14 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
resourceCursorAdapter.swapCursor(null); resourceCursorAdapter.swapCursor(null);
} }
} }
@Override
public void onTrackRecordingPaused(boolean isPaused) {
recordingTrackPaused = isPaused;
}
@Override
public void onTrackRecordingId(Track.Id trackId) {
recordingTrackId = trackId;
}
} }
@@ -24,10 +24,10 @@ public class Startup extends Application {
public void onCreate() { public void onCreate() {
super.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); 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); SharedPreferences sharedPreferences = PreferencesUtils.resetPreferences(this, false);
if (PreferencesUtils.getString(sharedPreferences, this, R.string.stats_units_key, "").equals("")) { 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); 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); ActivityUtils.applyNightMode(sharedPreferences, this);
//TODO Workaround to reset recordingTrackId on app startup as the TrackRecordingService (likely) crashed. // In debug builds: show thread and VM warnings.
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.
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Log.d(TAG, "Enabling strict mode"); Log.d(TAG, "Enabling strict mode");
StrictMode.enableDefaults(); StrictMode.enableDefaults();
@@ -129,7 +129,7 @@ public class TrackController implements View.OnTouchListener {
buttonDelay = () -> { buttonDelay = () -> {
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
view.performClick(); view.performClick();
callback.recordStart(); //TODO Should be recordPause(); callback.recordPause();
transition.resetTransition(); transition.resetTransition();
@@ -239,7 +239,7 @@ public class TrackController implements View.OnTouchListener {
public interface Callback { public interface Callback {
void recordStart(); void recordStart();
void recordPause();
void recordStop(); void recordStop();
} }
} }
@@ -52,6 +52,7 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns; import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.databinding.TrackListBinding; import de.dennisguse.opentracks.databinding.TrackListBinding;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue; import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
@@ -69,7 +70,7 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
* *
* @author Leif Hendrik Wilden * @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(); private static final String TAG = TrackListActivity.class.getSimpleName();
@@ -86,7 +87,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Preferences // Preferences
private boolean metricUnits = true; private boolean metricUnits = true;
private Track.Id recordingTrackId = null;
private boolean recordingTrackPaused = true;
// Callback when an item is selected in the contextual action mode // Callback when an item is selected in the contextual action mode
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() { 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() { private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) { if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, TrackListActivity.this); 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) { if (key != null) {
runOnUiThread(() -> { runOnUiThread(() -> {
TrackListActivity.this.invalidateOptionsMenu(); TrackListActivity.this.invalidateOptionsMenu();
loaderCallbacks.restart(); 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() { private final Runnable bindChangedCallback = new Runnable() {
@Override @Override
public void run() { public void run() {
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
// After binding changes (e.g., becomes available), update the total time in trackController. // 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(); TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) { if (service == null) {
@@ -155,11 +143,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Get GPS status and listen GPS status changes. // Get GPS status and listen GPS status changes.
gpsStatusValue = service.getGpsStatus(); gpsStatusValue = service.getGpsStatus();
updateGpsMenuItem(true, isRecording); updateGpsMenuItem(true, isRecording());
service.addListener(newStatus -> { service.addListener(TrackListActivity.this);
gpsStatusValue = newStatus;
updateGpsMenuItem(true, isRecording);
});
if (isGpsStarted()) { if (isGpsStarted()) {
return; return;
@@ -167,7 +152,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
service.startGps(); service.startGps();
gpsStatusValue = GpsStatusValue.GPS_ENABLED; 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; gpsStatusValue = GpsStatusValue.GPS_NONE;
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback); trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
@@ -197,7 +180,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView); viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> { viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> {
if (trackId == recordingTrackId.getId()) { if (isRecording() && trackId == recordingTrackId.getId()) {
// Is recording -> open record activity. // Is recording -> open record activity.
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class) Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId)); .putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
@@ -281,7 +264,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Update UI // Update UI
this.invalidateOptionsMenu(); this.invalidateOptionsMenu();
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks); LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
trackController.onResume(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused); trackController.onResume(isRecording(), recordingTrackPaused);
} }
@Override @Override
@@ -322,9 +305,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
boolean isGpsStarted = isGpsStarted(); updateMenuItems(isGpsStarted(), isRecording());
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
updateMenuItems(isGpsStarted, isRecording);
SearchView searchView = (SearchView) searchMenuItem.getActionView(); SearchView searchView = (SearchView) searchMenuItem.getActionView();
searchView.setQuery("", false); searchView.setQuery("", false);
@@ -572,7 +553,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
@Override @Override
public void recordStart() { public void recordStart() {
if (!PreferencesUtils.isRecording(recordingTrackId)) { if (recordingTrackId == null) {
// Not recording -> Recording // Not recording -> Recording
updateMenuItems(false, true); updateMenuItems(false, true);
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class); Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
@@ -582,17 +563,45 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
updateMenuItems(false, true); updateMenuItems(false, true);
trackRecordingServiceConnection.resumeTrack(); trackRecordingServiceConnection.resumeTrack();
trackController.update(true, false); 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 @Override
public void recordStop() { public void recordStop() {
updateMenuItems(false, false); 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;
} }
} }
@@ -40,11 +40,12 @@ import de.dennisguse.opentracks.fragments.ChartFragment;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.IntervalsFragment; import de.dennisguse.opentracks.fragments.IntervalsFragment;
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment; import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.settings.SettingsActivity; import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.IntentDashboardUtils; import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils; 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. * 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 Leif Hendrik Wilden
* @author Rodrigo Damazio * @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(); private static final String TAG = TrackRecordedActivity.class.getSimpleName();
@@ -71,6 +72,18 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
private Track.Id trackId; 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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -88,6 +101,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY)); viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
} }
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback);
postponeEnterTransition(); postponeEnterTransition();
} }
@@ -107,11 +122,14 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
if (trackId != null) { if (trackId != null) {
trackDataHub.loadTrack(trackId); trackDataHub.loadTrack(trackId);
} }
trackRecordingServiceConnection.bind(this);
} }
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
trackRecordingServiceConnection.unbind(this);
trackDataHub.stop(); trackDataHub.stop();
} }
@@ -143,7 +161,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { 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_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); Track track = contentProviderUtils.getTrack(trackId);
setTitle(track != null ? track.getName() : ""); setTitle(track != null ? track.getName() : "");
return super.onPrepareOptionsMenu(menu); return super.onPrepareOptionsMenu(menu);
@@ -285,4 +303,9 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
ViewCompat.setTransitionName(viewIcon, TrackRecordedActivity.VIEW_TRACK_ICON); ViewCompat.setTransitionName(viewIcon, TrackRecordedActivity.VIEW_TRACK_ICON);
startPostponedEnterTransition(); startPostponedEnterTransition();
} }
@Override
public void onTrackRecordingId(Track.Id trackId) {
recordingTrackId = trackId;
}
} }
@@ -26,6 +26,7 @@ import de.dennisguse.opentracks.fragments.ChartFragment;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.fragments.IntervalsFragment; import de.dennisguse.opentracks.fragments.IntervalsFragment;
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment; import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.settings.SettingsActivity; 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. //NOTE: This activity does NOT react to preference changes of R.string.recording_track_id_key.
//This mode of communication should be removed anyhow. //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"; 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 // Initialized from Intent; if a new track recording is started, a new TrackId will be provided by TrackRecordingService
private Track.Id trackId; private Track.Id trackId;
// Preferences
private boolean recordingTrackPaused; private boolean recordingTrackPaused;
private final Runnable bindChangedCallback = new Runnable() { private final Runnable bindChangedCallback = new Runnable() {
@@ -78,7 +77,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
return; return;
} }
if (!service.isRecording()) { if (!service.isRecording()) {
// Starts or resumes a track.
if (trackId == null) { if (trackId == null) {
// trackId isn't initialized -> leads a new recording. // trackId isn't initialized -> leads a new recording.
trackId = service.startNewTrack(); trackId = service.startNewTrack();
@@ -92,31 +90,23 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
trackController.update(true, false); trackController.update(true, false);
trackController.onResume(true, recordingTrackPaused); trackController.onResume(true, recordingTrackPaused);
} }
service.addListener(TrackRecordingActivity.this);
} }
}; };
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 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)) { if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
setLockscreenPolicy(); setLockscreenPolicy();
} }
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) { if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
setScreenOnPolicy(); setScreenOnPolicy();
} }
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_fullscreen_while_recording_key, key)) { if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_fullscreen_while_recording_key, key)) {
setFullscreenPolicy(); setFullscreenPolicy();
} }
if (key == null) return; if (key == null) return;
runOnUiThread(() -> { runOnUiThread(() -> {
TrackRecordingActivity.this.invalidateOptionsMenu(); TrackRecordingActivity.this.invalidateOptionsMenu();
trackController.update(true, recordingTrackPaused); trackController.update(true, recordingTrackPaused);
@@ -145,8 +135,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
} }
} }
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback); trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
trackDataHub = new TrackDataHub(this); trackDataHub = new TrackDataHub(this);
@@ -345,22 +333,21 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
@Override @Override
public void recordStart() { public void recordStart() {
if (recordingTrackPaused) { updateMenuItems(false);
// Paused -> Resume trackRecordingServiceConnection.resumeTrack();
updateMenuItems(false); trackController.update(true, false);
trackRecordingServiceConnection.resumeTrack(); }
trackController.update(true, false);
} else { @Override
// Recording -> Paused public void recordPause() {
updateMenuItems(true); updateMenuItems(true);
trackRecordingServiceConnection.pauseTrack(); trackRecordingServiceConnection.pauseTrack();
trackController.update(true, true); trackController.update(true, true);
}
} }
@Override @Override
public void recordStop() { public void recordStop() {
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this, true); trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this);
Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackRecordedActivity.class) Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackRecordedActivity.class)
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId); .putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
startActivity(newIntent); 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);
}
}
} }
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.content;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.database.ContentObserver; import android.database.ContentObserver;
import android.database.Cursor; import android.database.Cursor;
import android.os.Handler; import android.os.Handler;
@@ -31,7 +30,6 @@ import androidx.annotation.VisibleForTesting;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker; import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.MarkerColumns; import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track; 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.data.TracksColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator; 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. * Track data hub.
@@ -50,7 +50,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
* *
* @author Rodrigo Damazio * @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). * 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 ContentProviderUtils contentProviderUtils;
private final int targetNumPoints; private final int targetNumPoints;
private SharedPreferences sharedPreferences;
private boolean started; private boolean started;
private HandlerThread handlerThread; private HandlerThread handlerThread;
private Handler handler; private Handler handler;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
// Preference values // Preference values
private Track.Id selectedTrackId; private Track.Id selectedTrackId;
private Track.Id recordingTrackId; private Track.Id recordingTrackId;
@@ -92,6 +92,13 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
private ContentObserver markersTableObserver; private ContentObserver markersTableObserver;
private ContentObserver trackPointsTableObserver; private ContentObserver trackPointsTableObserver;
private final Runnable bindCallback = () -> {
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) {
service.addListener(TrackDataHub.this);
}
};
public TrackDataHub(Context context) { public TrackDataHub(Context context) {
this(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACKPOINTS); 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); contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback);
sharedPreferences = PreferencesUtils.getSharedPreferences(context); trackRecordingServiceConnection.bind(context);
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
onSharedPreferenceChanged(sharedPreferences, null);
handler.post(() -> { handler.post(() -> {
if (started) { if (started) {
@@ -159,9 +164,6 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
return; return;
} }
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
sharedPreferences = null;
started = false; started = false;
//Unregister listeners //Unregister listeners
@@ -175,6 +177,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
handlerThread = null; handlerThread = null;
} }
handler = null; handler = null;
trackRecordingServiceConnection.unbind(context);
} }
public void loadTrack(final @NonNull Track.Id trackId) { 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. * Returns true if the selected track is recording.
*/ */
public boolean isSelectedTrackRecording() { 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; 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. * 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; firstSeenTrackPointId = null;
lastSeenTrackPointId = null; lastSeenTrackPointId = null;
} }
@Override
public void onTrackRecordingPaused(boolean isPaused) {
recordingTrackPaused = isPaused;
}
@Override
public void onTrackRecordingId(Track.Id trackId) {
recordingTrackId = trackId;
}
} }
@@ -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[] projection = {MarkerColumns._ID};
String selection = MarkerColumns.TRACKID + "=?"; String selection = MarkerColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())}; String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
@@ -373,7 +373,7 @@ public class ContentProviderUtils {
return cursor.getCount(); return cursor.getCount();
} }
} }
return -1; return null;
} }
public Marker getMarker(@NonNull Marker.Id markerId) { public Marker getMarker(@NonNull Marker.Id markerId) {
@@ -40,7 +40,6 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter; import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/** /**
* Imports a KMZ file. * Imports a KMZ file.
@@ -79,7 +78,6 @@ public class KmzTrackImporter implements TrackImporter {
trackIdsWithImages.add(trackId); trackIdsWithImages.add(trackId);
deleteOrphanImages(context, trackId); deleteOrphanImages(context, trackId);
} else { } else {
cleanImport(context, trackId);
return new ArrayList<>(); 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<Track.Id> parseKml(ZipInputStream zipInputStream) { private List<Track.Id> parseKml(ZipInputStream zipInputStream) {
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context); KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context);
@@ -37,8 +37,6 @@ import androidx.core.app.TaskStackBuilder;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackListActivity; 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.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider; 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.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.io.file.exporter.ExportServiceResultReceiver; import de.dennisguse.opentracks.io.file.exporter.ExportServiceResultReceiver;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue; import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
@@ -83,23 +80,13 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private TrackRecordingServiceNotificationManager notificationManager; private TrackRecordingServiceNotificationManager notificationManager;
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
private Track.Id recordingTrackId;
private boolean recordingTrackPaused;
private int recordingDistanceInterval; private int recordingDistanceInterval;
private int maxRecordingDistance; private int maxRecordingDistance;
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Context context = TrackRecordingService.this; 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)) { if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context); boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
voiceExecutor.setMetricUnits(metricUnits); voiceExecutor.setMetricUnits(metricUnits);
@@ -132,7 +119,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private HandlerServer handlerServer; private HandlerServer handlerServer;
private List<TrackRecordingServiceCallback> listeners = new ArrayList<>(); private final TrackRecordingServiceStatus serviceStatus = new TrackRecordingServiceStatus();
@Override @Override
public void onCreate() { public void onCreate() {
@@ -145,14 +132,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
notificationManager = new TrackRecordingServiceNotificationManager(this); notificationManager = new TrackRecordingServiceNotificationManager(this);
// onSharedPreferenceChanged might not set recordingTrackId.
recordingTrackId = null;
sharedPreferences = PreferencesUtils.getSharedPreferences(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null); sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
restartTrackAfterServiceRestart();
} }
@Override @Override
@@ -167,13 +149,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
@Override @Override
public void onDestroy() { public void onDestroy() {
if (listeners != null) { serviceStatus.onStop();
for (TrackRecordingServiceCallback listener : listeners) {
listener.onGpsStatusChange(GpsStatusValue.GPS_NONE);
}
listeners.clear();
listeners = null;
}
handlerServer.stop(this); handlerServer.stop(this);
handlerServer = null; handlerServer = null;
@@ -212,15 +188,11 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
} }
public boolean isRecording() { public boolean isRecording() {
return PreferencesUtils.isRecording(recordingTrackId); return serviceStatus.isRecording();
} }
public boolean isPaused() { public boolean isPaused() {
return recordingTrackPaused; return serviceStatus.getRecordingTrackPaused();
}
public Track.Id getRecordingTrackId() {
return recordingTrackId;
} }
public TrackStatistics getTrackStatistics() { public TrackStatistics getTrackStatistics() {
@@ -250,14 +222,14 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
} }
if (name == null) { if (name == null) {
int nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(recordingTrackId); Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(serviceStatus.getRecordingTrackId());
if (nextMarkerNumber == -1) { if (nextMarkerNumber == null) {
nextMarkerNumber = 1; nextMarkerNumber = 1;
} }
name = getString(R.string.marker_name_format, nextMarkerNumber + 1); name = getString(R.string.marker_name_format, nextMarkerNumber + 1);
} }
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(recordingTrackId); TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(serviceStatus.getRecordingTrackId());
if (trackPoint == null) { if (trackPoint == null) {
Log.i(TAG, "Could not create a marker as trackPoint is unknown."); Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
return null; return null;
@@ -273,7 +245,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
long duration = stats.getTotalTime().toMillis(); long duration = stats.getTotalTime().toMillis();
// Insert marker // 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); Uri uri = contentProviderUtils.insertMarker(marker);
return new Marker.Id(ContentUris.parseId(uri)); return new Marker.Id(ContentUris.parseId(uri));
} }
@@ -294,8 +266,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
Uri uri = contentProviderUtils.insertTrack(track); Uri uri = contentProviderUtils.insertTrack(track);
Track.Id trackId = new Track.Id(ContentUris.parseId(uri)); Track.Id trackId = new Track.Id(ContentUris.parseId(uri));
// Update shared preferences // Set recording status
updateRecordingState(trackId, false); serviceStatus.onChange(trackId, false);
// Update database // Update database
track.setId(trackId); track.setId(trackId);
@@ -336,48 +308,23 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
insertTrackPoint(track, TrackPoint.createSegmentStartManual()); insertTrackPoint(track, TrackPoint.createSegmentStartManual());
// Update shared preferences. // Set recording status
updateRecordingState(trackId, false); serviceStatus.onChange(trackId, false);
startRecording(); 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() { void resumeCurrentTrack() {
if (!isRecording() || !isPaused()) { if (!isRecording() || !isPaused()) {
Log.d(TAG, "Ignore resumeCurrentTrack. Not recording or not paused."); Log.d(TAG, "Ignore resumeCurrentTrack. Not recording or not paused.");
return; return;
} }
updateRecordingState(recordingTrackId, false); // Set recording status
serviceStatus.onChange(false);
// Update database // Update database
Track track = contentProviderUtils.getTrack(recordingTrackId); Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId());
if (track != null) { if (track != null) {
insertTrackPoint(track, TrackPoint.createSegmentStartManual()); insertTrackPoint(track, TrackPoint.createSegmentStartManual());
} }
@@ -417,17 +364,18 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
showNotification(true); showNotification(true);
} }
void endCurrentTrack() { Track.Id endCurrentTrack() {
if (!isRecording()) { if (!isRecording()) {
Log.d(TAG, "Ignore endCurrentTrack. Not recording."); 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(); boolean wasPause = isPaused();
Track.Id trackId = recordingTrackId; Track.Id trackId = serviceStatus.getRecordingTrackId();
updateRecordingState(null, true); // Set recording status
serviceStatus.onChange(null, true);
if (!wasPause) { if (!wasPause) {
// Update database // Update database
@@ -445,6 +393,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
ExportUtils.postWorkoutExport(this, track, new ExportServiceResultReceiver(new Handler(), this)); ExportUtils.postWorkoutExport(this, track, new ExportServiceResultReceiver(new Handler(), this));
endRecording(true); endRecording(true);
return trackId;
} }
void pauseCurrentTrack() { void pauseCurrentTrack() {
@@ -453,10 +403,11 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return; return;
} }
updateRecordingState(recordingTrackId, true); // Set recording status
serviceStatus.onChange(true);
// Update database // Update database
Track track = contentProviderUtils.getTrack(recordingTrackId); Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId());
if (track != null) { if (track != null) {
if (lastTrackPoint != null) { if (lastTrackPoint != null) {
insertTrackPointIfNewer(track, lastTrackPoint); insertTrackPointIfNewer(track, lastTrackPoint);
@@ -528,21 +479,6 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return lastTrackPoint != null; 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 @Override
public void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy) { public void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy) {
if (!isRecording() || isPaused()) { if (!isRecording() || isPaused()) {
@@ -550,7 +486,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return; return;
} }
Track track = contentProviderUtils.getTrack(recordingTrackId); Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId());
if (track == null) { if (track == null) {
Log.w(TAG, "Ignore newTrackPoint. No track."); Log.w(TAG, "Ignore newTrackPoint. No track.");
return; return;
@@ -623,16 +559,12 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
@Override @Override
public void newGpsStatus(GpsStatusValue gpsStatusValue) { public void newGpsStatus(GpsStatusValue gpsStatusValue) {
if (listeners != null) { notificationManager.updateContent(getString(gpsStatusValue.message));
notificationManager.updateContent(getString(gpsStatusValue.message)); serviceStatus.onChange(gpsStatusValue);
for (TrackRecordingServiceCallback listener : listeners) {
listener.onGpsStatusChange(gpsStatusValue);
}
}
} }
public void addListener(TrackRecordingServiceCallback listener) { public void addListener(@NonNull TrackRecordingServiceStatus.Listener listener) {
listeners.add(listener); serviceStatus.addListener(listener);
} }
/** /**
@@ -724,7 +656,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private void showNotification(boolean isGpsStarted) { private void showNotification(boolean isGpsStarted) {
if (isRecording()) { if (isRecording()) {
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class) 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) PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addParentStack(TrackRecordingActivity.class) .addParentStack(TrackRecordingActivity.class)
.addNextIntent(intent) .addNextIntent(intent)
@@ -1,5 +1,6 @@
package de.dennisguse.opentracks.services; package de.dennisguse.opentracks.services;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import java.time.Duration; import java.time.Duration;
@@ -28,7 +29,7 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
} }
@Override @Override
public void addListener(TrackRecordingServiceCallback listener) { public void addListener(@NonNull TrackRecordingServiceStatus.Listener listener) {
trackRecordingService.addListener(listener); trackRecordingService.addListener(listener);
} }
@@ -67,8 +68,8 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
} }
@Override @Override
public void endCurrentTrack() { public Track.Id endCurrentTrack() {
trackRecordingService.endCurrentTrack(); return trackRecordingService.endCurrentTrack();
} }
@Override @Override
@@ -81,11 +82,6 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
return trackRecordingService.isPaused(); return trackRecordingService.isPaused();
} }
@Override
public Track.Id getRecordingTrackId() {
return trackRecordingService.getRecordingTrackId();
}
@Override @Override
public Duration getTotalTime() { public Duration getTotalTime() {
return trackRecordingService.getTotalTime(); return trackRecordingService.getTotalTime();
@@ -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);
}
@@ -20,7 +20,6 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.IBinder; import android.os.IBinder;
import android.os.IBinder.DeathRecipient; import android.os.IBinder.DeathRecipient;
import android.os.RemoteException; import android.os.RemoteException;
@@ -31,11 +30,7 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.BuildConfig; import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackEditActivity;
import de.dennisguse.opentracks.content.data.Marker; 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. * Wrapper for the track recording service.
@@ -64,6 +59,13 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
this.callback = callback; 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. * Starts and binds the service.
*/ */
@@ -202,30 +204,12 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
return null; return null;
} }
public void stopRecording(@NonNull Context context, boolean showEditor) { public void stopRecording(@NonNull Context context) {
TrackRecordingServiceInterface trackRecordingService = getServiceIfBound(); TrackRecordingServiceInterface trackRecordingService = getServiceIfBound();
if (trackRecordingService == null) { if (trackRecordingService == null) {
Log.e(TAG, "TrackRecordingService not connected."); Log.e(TAG, "TrackRecordingService not connected.");
} else { } else {
try { trackRecordingService.endCurrentTrack();
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);
}
} }
unbindAndStop(context); unbindAndStop(context);
} }
@@ -15,6 +15,7 @@
*/ */
package de.dennisguse.opentracks.services; package de.dennisguse.opentracks.services;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import java.time.Duration; import java.time.Duration;
@@ -44,7 +45,12 @@ public interface TrackRecordingServiceInterface {
void resumeTrack(Track.Id trackId); 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(); boolean isRecording();
@@ -53,11 +59,6 @@ public interface TrackRecordingServiceInterface {
*/ */
boolean isPaused(); 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. * Gets the total time for the current recording track. Returns 0 if not recording.
*/ */
@@ -100,7 +101,7 @@ public interface TrackRecordingServiceInterface {
@VisibleForTesting @VisibleForTesting
void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy); void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy);
void addListener(TrackRecordingServiceCallback listener); void addListener(@NonNull TrackRecordingServiceStatus.Listener listener);
GpsStatusValue getGpsStatus(); GpsStatusValue getGpsStatus();
} }
@@ -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<Listener> 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) {}
}
}
@@ -31,6 +31,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator; import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.AnnouncementUtils; import de.dennisguse.opentracks.util.AnnouncementUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -57,6 +58,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
private final ContentProviderUtils contentProviderUtils; private final ContentProviderUtils contentProviderUtils;
private Track.Id recordingTrackId;
private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override @Override
public void onAudioFocusChange(int focusChange) { public void onAudioFocusChange(int focusChange) {
@@ -139,6 +142,12 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
Log.e(TAG, "TrackRecordingService is null."); Log.e(TAG, "TrackRecordingService is null.");
return; return;
} }
trackRecordingService.addListener(new TrackRecordingServiceStatus.Listener() {
@Override
public void onTrackRecordingId(Track.Id trackId) {
recordingTrackId = trackId;
}
});
announce(trackRecordingService.getTrackStatistics()); announce(trackRecordingService.getTrackStatistics());
} }
@@ -171,7 +180,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
return; return;
} }
Track track = contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(sharedPreferences, context)); Track track = contentProviderUtils.getTrack(recordingTrackId);
String category = track != null ? track.getCategory() : ""; String category = track != null ? track.getCategory() : "";
//TODO Querying all TrackPoints all the time is inefficient; use TrackDataHub or something else. //TODO Querying all TrackPoints all the time is inefficient; use TrackDataHub or something else.
@@ -20,8 +20,12 @@ import java.util.Locale;
import de.dennisguse.opentracks.AbstractActivity; import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.io.file.TrackFileFormat; 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.BluetoothLeCyclingCadenceAndSpeedPreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference; import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference; 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(); 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 SharedPreferences sharedPreferences;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> { 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)) { if (PreferencesUtils.isKey(getActivity(), R.string.stats_units_key, key)) {
getActivity().runOnUiThread(this::updateUnits); 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. // Used to forward update from ChooseActivityTypeDialogFragment; TODO Could be replaced with LiveData.
private ActivityTypePreference.ActivityPreferenceDialog activityPreferenceDialog; private ActivityTypePreference.ActivityPreferenceDialog activityPreferenceDialog;
@@ -112,6 +126,8 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext()); sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindServiceCallback);
try { try {
setPreferencesFromResource(R.xml.settings, rootKey); setPreferencesFromResource(R.xml.settings, rootKey);
} catch (ClassCastException e) { } catch (ClassCastException e) {
@@ -137,13 +153,14 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
trackRecordingServiceConnection.bind(getContext());
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null); sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
updateUnits(); updateUnits();
updateReset(); updatePrefsDependOnRecording();
updateBluetooth(); updateBluetooth();
updatePostWorkoutExport(); updatePostWorkoutExport();
} }
@@ -186,6 +203,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
trackRecordingServiceConnection.unbind(getContext());
sharedPreferences = null; sharedPreferences = null;
} }
@@ -195,11 +213,18 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
} }
} }
private void updateReset() { private void updatePrefsDependOnRecording() {
final boolean isRecording = PreferencesUtils.isRecording(sharedPreferences, getActivity());
Preference resetPreference = findPreference(getString(R.string.settings_reset_key)); 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) : ""); 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); resetPreference.setEnabled(!isRecording);
importPreference.setEnabled(!isRecording);
exportPreference.setEnabled(!isRecording);
} }
private void updateBluetooth() { private void updateBluetooth() {
@@ -266,6 +291,14 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
return false; return false;
}); });
} }
@Override
public void onTrackRecordingId(Track.Id trackId) {
if (trackId != null && !isRecording && isAdded()) {
isRecording = true;
updatePrefsDependOnRecording();
}
}
} }
} }
@@ -27,7 +27,6 @@ import androidx.documentfile.provider.DocumentFile;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
/** /**
@@ -47,21 +46,6 @@ public class PreferencesUtils {
return PreferenceManager.getDefaultSharedPreferences(context); 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) { public static String getDefaultActivity(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.default_activity_key, context.getString(R.string.default_activity_default)); 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); 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) { private static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) {
try { try {
return sharedPreferences.getInt(getKey(context, keyId), defaultValue); 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) { public static String getString(SharedPreferences sharedPreferences, Context context, int keyId, String defaultValue) {
return sharedPreferences.getString(getKey(context, keyId), 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)); 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) { private static String getBluetoothSensorAddressNone(Context context) {
return context.getString(R.string.sensor_type_value_none); return context.getString(R.string.sensor_type_value_none);
} }
@@ -302,20 +246,6 @@ public class PreferencesUtils {
return Integer.parseInt(value); 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) { public static SharedPreferences resetPreferences(Context context, boolean readAgain) {
SharedPreferences sharedPreferences = getSharedPreferences(context); SharedPreferences sharedPreferences = getSharedPreferences(context);
if (readAgain) { if (readAgain) {
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
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.
-->
<resources>
<!-- Keys for persisted preferences that are used by internally. -->
<!--But they should not get backed up or restored.-->
<string name="recording_track_id_key" translatable="false">recordingTrackId</string>
<string name="recording_track_paused_key" translatable="false">recordingTrackPaused</string>
<bool name="recording_track_paused_default" translatable="false">true</bool>
</resources>
+3
View File
@@ -31,6 +31,9 @@
<string name="post_workout_export_enabled_key" translatable="false">instantExportEnabled</string> <string name="post_workout_export_enabled_key" translatable="false">instantExportEnabled</string>
<bool name="post_workout_export_enabled_default" translatable="false">false</bool> <bool name="post_workout_export_enabled_default" translatable="false">false</bool>
<string name="settings_import" translatable="false">settingsImport</string>
<string name="settings_export" translatable="false">settingsExport</string>
<string name="default_activity_key" translatable="false">defaultActivity</string> <string name="default_activity_key" translatable="false">defaultActivity</string>
<string name="default_activity_default" translatable="false">@string/activity_type_unknown</string> <string name="default_activity_default" translatable="false">@string/activity_type_unknown</string>
<string-array name="activity_types"> <string-array name="activity_types">
+6 -2
View File
@@ -148,12 +148,16 @@ limitations under the License.
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/generic_tracks"> <PreferenceCategory android:title="@string/generic_tracks">
<Preference android:title="@string/menu_import_all"> <Preference
android:key="@string/settings_import"
android:title="@string/menu_import_all">
<intent <intent
android:targetClass="de.dennisguse.opentracks.io.file.importer.DirectoryChooserActivity$ImportDirectoryChooserActivity" android:targetClass="de.dennisguse.opentracks.io.file.importer.DirectoryChooserActivity$ImportDirectoryChooserActivity"
android:targetPackage="@string/applicationId" /> android:targetPackage="@string/applicationId" />
</Preference> </Preference>
<Preference android:title="@string/menu_export_all"> <Preference
android:key="@string/settings_export"
android:title="@string/menu_export_all">
<intent <intent
android:targetClass="de.dennisguse.opentracks.io.file.importer.DirectoryChooserActivity$ExportDirectoryChooserActivity" android:targetClass="de.dennisguse.opentracks.io.file.importer.DirectoryChooserActivity$ExportDirectoryChooserActivity"
android:targetPackage="@string/applicationId" /> android:targetPackage="@string/applicationId" />