Use a non-static Binder for TrackRecordingService.

Fixes #98.
This commit is contained in:
Dennis Guse
2021-04-04 21:37:12 +02:00
parent 1c6012596e
commit 1e518a4c35
16 changed files with 102 additions and 309 deletions
@@ -40,7 +40,6 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter; import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -89,7 +88,8 @@ public class ExportImportTest {
@Before @Before
public void setUp() throws TimeoutException { public void setUp() throws TimeoutException {
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(new Intent(context, TrackRecordingService.class))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
trackId = service.startNewTrack(); trackId = service.startNewTrack();
@@ -113,7 +113,8 @@ public class TrackRecordingServiceTest {
@After @After
public void tearDown() throws TimeoutException { public void tearDown() throws TimeoutException {
// Reset service (if some previous test failed) // Reset service (if some previous test failed)
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
if (service.isRecording() || service.isPaused()) { if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack(); service.endCurrentTrack();
} }
@@ -146,7 +147,8 @@ public class TrackRecordingServiceTest {
// when // when
Intent startIntent = createStartIntent(context); Intent startIntent = createStartIntent(context);
mServiceRule.startService(startIntent); mServiceRule.startService(startIntent);
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent)); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(startIntent))
.getService();
// then // then
// Test if we start in no-recording mode by default. // Test if we start in no-recording mode by default.
@@ -160,7 +162,8 @@ public class TrackRecordingServiceTest {
createDummyTrack(trackId); createDummyTrack(trackId);
// when // when
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
// then // then
assertFalse(service.isRecording()); assertFalse(service.isRecording());
@@ -173,7 +176,8 @@ public class TrackRecordingServiceTest {
createDummyTrack(trackId); createDummyTrack(trackId);
//when //when
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
service.resumeTrack(trackId); service.resumeTrack(trackId);
// then // then
@@ -184,7 +188,8 @@ public class TrackRecordingServiceTest {
@Test @Test
public void testRecording_start() throws TimeoutException { public void testRecording_start() throws TimeoutException {
// given // given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
// when // when
Track.Id trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
@@ -200,7 +205,8 @@ public class TrackRecordingServiceTest {
@Test @Test
public void testRecording_stop() throws TimeoutException { public void testRecording_stop() throws TimeoutException {
// given // given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
// when // when
@@ -218,7 +224,8 @@ public class TrackRecordingServiceTest {
@Test @Test
public void testRecording_pauseAndResume() throws TimeoutException { public void testRecording_pauseAndResume() throws TimeoutException {
// given // given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
// when // when
@@ -244,7 +251,8 @@ public class TrackRecordingServiceTest {
@Test @Test
public void testRecording_resumeStoppedTrack() throws TimeoutException, InterruptedException { public void testRecording_resumeStoppedTrack() throws TimeoutException, InterruptedException {
// given // given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording()); assertTrue(service.isRecording());
service.endCurrentTrack(); service.endCurrentTrack();
@@ -270,7 +278,8 @@ public class TrackRecordingServiceTest {
@Test @Test
public void testRecording_stopPausedTrack() throws TimeoutException { public void testRecording_stopPausedTrack() throws TimeoutException {
// given // given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording()); assertTrue(service.isRecording());
service.pauseCurrentTrack(); service.pauseCurrentTrack();
@@ -293,7 +302,8 @@ public class TrackRecordingServiceTest {
@Test @Test
public void testStartNewTrack_alreadyRecording() throws TimeoutException { public void testStartNewTrack_alreadyRecording() throws TimeoutException {
// given // given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording()); assertTrue(service.isRecording());
@@ -309,7 +319,8 @@ public class TrackRecordingServiceTest {
@Test @Test
public void testEndCurrentTrack_noRecording() throws TimeoutException { public void testEndCurrentTrack_noRecording() throws TimeoutException {
// given // given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
assertFalse(service.isRecording()); assertFalse(service.isRecording());
// when // when
@@ -324,7 +335,8 @@ public class TrackRecordingServiceTest {
@Test @Test
public void testInsertWaypointMarker_noRecordingTrack() throws TimeoutException { public void testInsertWaypointMarker_noRecordingTrack() throws TimeoutException {
// given // given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
assertFalse(service.isRecording()); assertFalse(service.isRecording());
// when // when
@@ -338,7 +350,8 @@ public class TrackRecordingServiceTest {
@Test @Test
public void testInsertWaypointMarker_validWaypoint() throws TimeoutException, InterruptedException { public void testInsertWaypointMarker_validWaypoint() throws TimeoutException, InterruptedException {
// given // given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
.getService();
Track.Id trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
assertTrue(service.isRecording()); assertTrue(service.isRecording());
newTrackPoint(service); newTrackPoint(service);
@@ -377,18 +390,18 @@ public class TrackRecordingServiceTest {
addTrack(dummyTrack); addTrack(dummyTrack);
} }
private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException { private static void newTrackPoint(TrackRecordingService trackRecordingService) throws InterruptedException {
newTrackPoint(trackRecordingService, 45.0f, 35f, 5, 10, System.currentTimeMillis()); newTrackPoint(trackRecordingService, 45.0f, 35f, 5, 10, System.currentTimeMillis());
} }
static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed) throws InterruptedException { static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed) throws InterruptedException {
newTrackPoint(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis()); newTrackPoint(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
} }
/** /**
* Inserts a location and waits for 200ms. * Inserts a location and waits for 200ms.
*/ */
private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) throws InterruptedException { private static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) throws InterruptedException {
Location location = new Location(""); Location location = new Location("");
location.setLongitude(longitude); location.setLongitude(longitude);
location.setLatitude(latitude); location.setLatitude(latitude);
@@ -55,7 +55,7 @@ public class TrackRecordingServiceTestLocation {
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
private TrackRecordingServiceInterface service; private TrackRecordingService service;
@BeforeClass @BeforeClass
public static void preSetUp() { public static void preSetUp() {
@@ -77,7 +77,8 @@ public class TrackRecordingServiceTestLocation {
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context); SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().commit(); sharedPreferences.edit().clear().commit();
service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context))); service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
} }
@After @After
@@ -87,7 +87,8 @@ public class TrackRecordingServiceTestLooper {
@After @After
public void tearDown() throws TimeoutException { public void tearDown() throws TimeoutException {
// Reset service (if some previous test failed) // Reset service (if some previous test failed)
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
if (service.isRecording() || service.isPaused()) { if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack(); service.endCurrentTrack();
} }
@@ -219,7 +220,8 @@ public class TrackRecordingServiceTestLooper {
} }
private void fullRecordingSession() throws TimeoutException { private void fullRecordingSession() throws TimeoutException {
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context))); TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
assertFalse(service.isRecording()); assertFalse(service.isRecording());
// Start a track. // Start a track.
@@ -40,9 +40,9 @@ 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.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
import de.dennisguse.opentracks.util.ActivityUtils; import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
@@ -73,7 +73,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private final Runnable bindCallback = new Runnable() { private final Runnable bindCallback = new Runnable() {
@Override @Override
public void run() { public void run() {
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) { if (service != null) {
service.addListener(MarkerListActivity.this); service.addListener(MarkerListActivity.this);
} }
@@ -27,8 +27,8 @@ import android.view.accessibility.AccessibilityEvent;
import java.time.Duration; import java.time.Duration;
import de.dennisguse.opentracks.databinding.TrackControllerBinding; import de.dennisguse.opentracks.databinding.TrackControllerBinding;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; 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.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions; import de.dennisguse.opentracks.util.UnitConversions;
@@ -221,7 +221,7 @@ public class TrackController implements View.OnTouchListener {
} }
private void updateTotalTime() { private void updateTotalTime() {
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService != null) { if (trackRecordingService != null) {
totalTime = trackRecordingService.getTotalTime(); totalTime = trackRecordingService.getTotalTime();
} }
@@ -52,9 +52,9 @@ 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.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue; import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.settings.SettingsActivity; import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.ActivityUtils; import de.dennisguse.opentracks.util.ActivityUtils;
@@ -134,7 +134,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// 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(); TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) { if (service == null) {
Log.d(TAG, "service not available to start gps or a new recording"); Log.d(TAG, "service not available to start gps or a new recording");
gpsStatusValue = GpsStatusValue.GPS_NONE; gpsStatusValue = GpsStatusValue.GPS_NONE;
@@ -150,7 +150,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
return; return;
} }
service.startGps(); service.tryStartGps();
gpsStatusValue = GpsStatusValue.GPS_ENABLED; gpsStatusValue = GpsStatusValue.GPS_ENABLED;
updateGpsMenuItem(true, isRecording()); updateGpsMenuItem(true, isRecording());
} }
@@ -325,9 +325,9 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
trackRecordingServiceConnection.startAndBind(this); trackRecordingServiceConnection.startAndBind(this);
bindChangedCallback.run(); bindChangedCallback.run();
} else { } else {
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService != null) { if (trackRecordingService != null) {
trackRecordingService.stopGps(); trackRecordingService.stopGpsAndShutdown();
} }
trackRecordingServiceConnection.unbindAndStop(this); trackRecordingServiceConnection.unbindAndStop(this);
} }
@@ -40,9 +40,9 @@ 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.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
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;
@@ -77,7 +77,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
private final Runnable bindCallback = new Runnable() { private final Runnable bindCallback = new Runnable() {
@Override @Override
public void run() { public void run() {
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) { if (service != null) {
service.addListener(TrackRecordedActivity.this); service.addListener(TrackRecordedActivity.this);
} }
@@ -26,9 +26,9 @@ 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.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
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;
@@ -71,7 +71,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
// After binding changes (is available), update the total time in trackController. // After binding changes (is available), update the total time in trackController.
runOnUiThread(() -> trackController.update(true, recordingTrackPaused)); runOnUiThread(() -> trackController.update(true, recordingTrackPaused));
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) { if (service == null) {
Log.d(TAG, "could not get TrackRecordingService"); Log.d(TAG, "could not get TrackRecordingService");
return; return;
@@ -38,9 +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.services.TrackRecordingServiceStatus; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
/** /**
* Track data hub. * Track data hub.
@@ -93,7 +93,7 @@ public class TrackDataHub implements TrackRecordingServiceStatus.Listener {
private ContentObserver trackPointsTableObserver; private ContentObserver trackPointsTableObserver;
private final Runnable bindCallback = () -> { private final Runnable bindCallback = () -> {
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) { if (service != null) {
service.addListener(TrackDataHub.this); service.addListener(TrackDataHub.this);
} }
@@ -30,8 +30,8 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling; import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding; import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
@@ -306,12 +306,12 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
* Also sets altitude gain and loss. * Also sets altitude gain and loss.
*/ */
private void updateSensorDataUI() { private void updateSensorDataUI() {
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService == null) { if (trackRecordingService == null) {
Log.d(TAG, "Cannot get the track recording service."); Log.d(TAG, "Cannot get the track recording service.");
} else { } else {
SensorDataSet sensorDataSet = trackRecordingService.getSensorData(); SensorDataSet sensorDataSet = trackRecordingService.getSensorDataSet();
if (sensorDataSet != null) { if (sensorDataSet != null) {
List<SensorDataModel> sensorDataList = new ArrayList<>(); List<SensorDataModel> sensorDataList = new ArrayList<>();
if (sensorDataSet.getHeartRate() != null) { if (sensorDataSet.getHeartRate() != null) {
@@ -438,7 +438,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
private void updateTotalTime() { private void updateTotalTime() {
Duration totalTime = lastTrackStatistics.getTotalTime(); Duration totalTime = lastTrackStatistics.getTotalTime();
if (isSelectedTrackRecording()) { if (isSelectedTrackRecording()) {
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService != null) { if (trackRecordingService != null) {
totalTime = trackRecordingService.getTotalTime(); totalTime = trackRecordingService.getTotalTime();
} }
@@ -27,7 +27,6 @@ import android.database.sqlite.SQLiteException;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager.WakeLock; import android.os.PowerManager.WakeLock;
import android.util.Log; import android.util.Log;
@@ -115,7 +114,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private TrackPoint lastTrackPoint; private TrackPoint lastTrackPoint;
private boolean isIdle; private boolean isIdle;
private TrackRecordingServiceBinder binder = new TrackRecordingServiceBinder(this); private final Binder binder = new Binder();
private HandlerServer handlerServer; private HandlerServer handlerServer;
@@ -143,7 +142,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
} }
@Override @Override
public IBinder onBind(Intent intent) { public Binder onBind(Intent intent) {
return binder; return binder;
} }
@@ -178,9 +177,6 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
contentProviderUtils = null; contentProviderUtils = null;
binder.detachFromService();
binder = null;
// This should be the next to last operation // This should be the next to last operation
wakeLock = SystemUtils.releaseWakeLock(wakeLock); wakeLock = SystemUtils.releaseWakeLock(wakeLock);
@@ -255,7 +251,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
* *
* @return the track id * @return the track id
*/ */
Track.Id startNewTrack() { public Track.Id startNewTrack() {
if (isRecording()) { if (isRecording()) {
Log.d(TAG, "Ignore startNewTrack. Already recording."); Log.d(TAG, "Ignore startNewTrack. Already recording.");
return null; return null;
@@ -295,7 +291,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
* *
* @param trackId the id of the track to be resumed. * @param trackId the id of the track to be resumed.
*/ */
void resumeTrack(Track.Id trackId) { public void resumeTrack(Track.Id trackId) {
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
if (track == null) { if (track == null) {
Log.e(TAG, "Ignore resumeTrack. Track " + trackId.getId() + " does not exists."); Log.e(TAG, "Ignore resumeTrack. Track " + trackId.getId() + " does not exists.");
@@ -314,7 +310,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
startRecording(); startRecording();
} }
void resumeCurrentTrack() { @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public 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;
@@ -352,7 +349,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
voiceExecutor.restore(); voiceExecutor.restore();
} }
void tryStartGps() { public void tryStartGps() {
if (isRecording()) return; if (isRecording()) return;
startGps(); startGps();
@@ -364,7 +361,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
showNotification(true); showNotification(true);
} }
Track.Id endCurrentTrack() { @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public Track.Id endCurrentTrack() {
if (!isRecording()) { if (!isRecording()) {
Log.d(TAG, "Ignore endCurrentTrack. Not recording."); Log.d(TAG, "Ignore endCurrentTrack. Not recording.");
return null; return null;
@@ -397,7 +395,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return trackId; return trackId;
} }
void pauseCurrentTrack() { @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public void pauseCurrentTrack() {
if (!isRecording() || isPaused()) { if (!isRecording() || isPaused()) {
Log.d(TAG, "Ignore pauseCurrentTrack. Not recording or paused."); Log.d(TAG, "Ignore pauseCurrentTrack. Not recording or paused.");
return; return;
@@ -446,6 +445,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
stopGps(trackStopped); stopGps(trackStopped);
} }
public void stopGpsAndShutdown() {
stopGps(true);
}
/** /**
* Stops gps. * Stops gps.
* *
@@ -616,7 +619,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
voiceExecutor.update(); voiceExecutor.update();
} }
SensorDataSet getSensorDataSet() { public SensorDataSet getSensorDataSet() {
if (remoteSensorManager == null) { if (remoteSensorManager == null) {
return null; return null;
} }
@@ -634,7 +637,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
/** /**
* Returns the relative altitude gain (since last trackpoint). * Returns the relative altitude gain (since last trackpoint).
*/ */
Float getAltitudeGain_m() { public Float getAltitudeGain_m() {
if (altitudeSumManager == null || !altitudeSumManager.isConnected()) { if (altitudeSumManager == null || !altitudeSumManager.isConnected()) {
return null; return null;
} }
@@ -645,7 +648,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
/** /**
* Returns the relative altitude loss (since last trackpoint). * Returns the relative altitude loss (since last trackpoint).
*/ */
Float getAltitudeLoss_m() { public Float getAltitudeLoss_m() {
if (altitudeSumManager == null || !altitudeSumManager.isConnected()) { if (altitudeSumManager == null || !altitudeSumManager.isConnected()) {
return null; return null;
} }
@@ -702,4 +705,14 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
} }
} }
} public class Binder extends android.os.Binder {
private Binder() {
super();
}
public TrackRecordingService getService() {
return TrackRecordingService.this;
}
}
}
@@ -1,129 +0,0 @@
package de.dennisguse.opentracks.services;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.time.Duration;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
/**
* TODO: There is a bug in Android that leaks Binder instances. This bug is
* especially visible if we have a non-static class, as there is no way to
* nullify reference to the outer class (the service). A workaround is to use
* a static class and explicitly clear service and detach it from the
* underlying Binder. With this approach, we minimize the leak to 24 bytes per
* each service instance. For more details, see the following bug:
* http://code.google.com/p/android/issues/detail?id=6426.
*/
class TrackRecordingServiceBinder extends android.os.Binder implements TrackRecordingServiceInterface {
private TrackRecordingService trackRecordingService;
TrackRecordingServiceBinder(TrackRecordingService trackRecordingService) {
this.trackRecordingService = trackRecordingService;
}
@Override
public void addListener(@NonNull TrackRecordingServiceStatus.Listener listener) {
trackRecordingService.addListener(listener);
}
@Override
public GpsStatusValue getGpsStatus() {
return trackRecordingService.getGpsStatus();
}
@Override
public void startGps() {
trackRecordingService.tryStartGps();
}
public void stopGps() {
trackRecordingService.stopGps(true);
}
@Override
public Track.Id startNewTrack() {
return trackRecordingService.startNewTrack();
}
@Override
public void resumeTrack(Track.Id trackId) {
trackRecordingService.resumeTrack(trackId);
}
@Override
public void pauseCurrentTrack() {
trackRecordingService.pauseCurrentTrack();
}
@Override
public void resumeCurrentTrack() {
trackRecordingService.resumeCurrentTrack();
}
@Override
public Track.Id endCurrentTrack() {
return trackRecordingService.endCurrentTrack();
}
@Override
public boolean isRecording() {
return trackRecordingService.isRecording();
}
@Override
public boolean isPaused() {
return trackRecordingService.isPaused();
}
@Override
public Duration getTotalTime() {
return trackRecordingService.getTotalTime();
}
@Override
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
return trackRecordingService.insertMarker(name, category, description, photoUrl);
}
@Override
public SensorDataSet getSensorData() {
return trackRecordingService.getSensorDataSet();
}
@Override
public Float getAltitudeGain_m() {
return trackRecordingService.getAltitudeGain_m();
}
@Override
public Float getAltitudeLoss_m() {
return trackRecordingService.getAltitudeLoss_m();
}
@VisibleForTesting
@Override
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
trackRecordingService.setRemoteSensorManager(remoteSensorManager);
}
/**
* Detaches from the track recording service. Clears the reference to the
* outer class to minimize the leak.
*/
void detachFromService() {
trackRecordingService = null;
}
@VisibleForTesting
@Override
public void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy) {
trackRecordingService.newTrackPoint(trackPoint, recordingGpsAccuracy);
}
}
@@ -46,7 +46,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
private final Runnable callback; private final Runnable callback;
private TrackRecordingServiceInterface trackRecordingService; private TrackRecordingService trackRecordingService;
public TrackRecordingServiceConnection() { public TrackRecordingServiceConnection() {
callback = null; callback = null;
@@ -120,7 +120,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
/** /**
* Gets the track recording service if bound. Returns null otherwise * Gets the track recording service if bound. Returns null otherwise
*/ */
public TrackRecordingServiceInterface getServiceIfBound() { public TrackRecordingService getServiceIfBound() {
return trackRecordingService; return trackRecordingService;
} }
@@ -129,7 +129,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* *
* @param value the value * @param value the value
*/ */
private void setTrackRecordingService(TrackRecordingServiceInterface value) { private void setTrackRecordingService(TrackRecordingService value) {
trackRecordingService = value; trackRecordingService = value;
if (callback != null) { if (callback != null) {
callback.run(); callback.run();
@@ -144,7 +144,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Failed to bind a death recipient.", e); Log.e(TAG, "Failed to bind a death recipient.", e);
} }
setTrackRecordingService((TrackRecordingServiceInterface) service); setTrackRecordingService(((TrackRecordingService.Binder) service).getService());
} }
@Override @Override
@@ -163,7 +163,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* Resumes the recording track. * Resumes the recording track.
*/ */
public void resumeTrack() { public void resumeTrack() {
TrackRecordingServiceInterface service = getServiceIfBound(); TrackRecordingService service = getServiceIfBound();
if (service != null) { if (service != null) {
service.resumeCurrentTrack(); service.resumeCurrentTrack();
} }
@@ -173,7 +173,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* Pauses the recording track. * Pauses the recording track.
*/ */
public void pauseTrack() { public void pauseTrack() {
TrackRecordingServiceInterface service = getServiceIfBound(); TrackRecordingService service = getServiceIfBound();
if (service != null) { if (service != null) {
service.pauseCurrentTrack(); service.pauseCurrentTrack();
} }
@@ -185,7 +185,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* @return the id of the marker or null if none could be created. * @return the id of the marker or null if none could be created.
*/ */
public Marker.Id addMarker(Context context, String name, String category, String description, String photoUrl) { public Marker.Id addMarker(Context context, String name, String category, String description, String photoUrl) {
TrackRecordingServiceInterface trackRecordingService = getServiceIfBound(); TrackRecordingService trackRecordingService = getServiceIfBound();
if (trackRecordingService == null) { if (trackRecordingService == null) {
Log.d(TAG, "Unable to add marker, no track recording service"); Log.d(TAG, "Unable to add marker, no track recording service");
} else { } else {
@@ -205,7 +205,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
} }
public void stopRecording(@NonNull Context context) { public void stopRecording(@NonNull Context context) {
TrackRecordingServiceInterface trackRecordingService = getServiceIfBound(); TrackRecordingService trackRecordingService = getServiceIfBound();
if (trackRecordingService == null) { if (trackRecordingService == null) {
Log.e(TAG, "TrackRecordingService not connected."); Log.e(TAG, "TrackRecordingService not connected.");
} else { } else {
@@ -1,107 +0,0 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.services;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.time.Duration;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
/**
* App's service.
* This service is the process that actually records and manages tracks.
*/
public interface TrackRecordingServiceInterface {
void startGps();
void stopGps();
Track.Id startNewTrack();
void pauseCurrentTrack();
void resumeCurrentTrack();
void resumeTrack(Track.Id trackId);
/**
* End current track.
*
* @return the Track.Id of the track that is ended.
*/
Track.Id endCurrentTrack();
boolean isRecording();
/**
* Returns true if the current recording track is paused. Returns true if not recording.
*/
boolean isPaused();
/**
* Gets the total time for the current recording track. Returns 0 if not recording.
*/
Duration getTotalTime();
Marker.Id insertMarker(String name, String category, String description, String photoUrl);
/**
* Gets the current sensor data.
*
* @return SensorDataSet object or null.
*/
SensorDataSet getSensorData();
/**
* Gets the current altitude gain.
*
* @return altitude gain in m or null.
*/
Float getAltitudeGain_m();
/**
* Gets the current altitude loss.
*
* @return altitude loss in m or null.
*/
Float getAltitudeLoss_m();
@VisibleForTesting
void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager);
/**
* Inserts a track point in the current recording track.
* This is used for inserting special track points or for testing.
*
* @param trackPoint the track point object to be inserted.
* @param recordingGpsAccuracy recording GPS accuracy.
*/
@Deprecated //REMOVE
@VisibleForTesting
void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy);
void addListener(@NonNull TrackRecordingServiceStatus.Listener listener);
GpsStatusValue getGpsStatus();
}
@@ -23,9 +23,9 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track; 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.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
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;
@@ -112,7 +112,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
private final Runnable bindServiceCallback = new Runnable() { private final Runnable bindServiceCallback = new Runnable() {
@Override @Override
public void run() { public void run() {
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) { if (service != null) {
service.addListener(PrefsFragment.this); service.addListener(PrefsFragment.this);
} }