TrackRecordingService: provide status updates via LiveData.

Fixes #719
This commit is contained in:
Dennis Guse
2021-04-29 18:22:35 +02:00
parent 8357b234d2
commit 6723bfb979
12 changed files with 246 additions and 274 deletions
@@ -44,6 +44,7 @@ import static org.junit.Assert.assertNotNull;
* So, if it is executed on a real device, data might be lost. * So, if it is executed on a real device, data might be lost.
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
//TODO Implement as mock test; no need to store data in database
public class TrackRecordingServiceTestLocation { public class TrackRecordingServiceTestLocation {
@Rule @Rule
@@ -18,6 +18,8 @@ package de.dennisguse.opentracks;
import android.content.Intent; import android.content.Intent;
import androidx.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -36,6 +38,8 @@ import de.dennisguse.opentracks.util.IntentUtils;
* *
* @author Jimmy Shih * @author Jimmy Shih
*/ */
//TODO It is actually not a List; we need a better name for this class
//TODO Check if this class is still such a good idea; inheritance might limit maintainability
public abstract class AbstractListActivity extends AbstractActivity implements ConfirmDeleteCaller { public abstract class AbstractListActivity extends AbstractActivity implements ConfirmDeleteCaller {
private static final String TAG = AbstractListActivity.class.getSimpleName(); private static final String TAG = AbstractListActivity.class.getSimpleName();
@@ -43,8 +47,6 @@ 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) {
@@ -68,7 +70,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
boolean stopRecording = false; boolean stopRecording = false;
for (Track.Id trackId : trackIds) { for (Track.Id trackId : trackIds) {
if (trackId.equals(recordingTrackId)) { if (trackId.equals(getRecordingTrackId())) {
stopRecording = true; stopRecording = true;
break; break;
} }
@@ -87,10 +89,17 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
* Gets the track recording service connection. * Gets the track recording service connection.
* For stopping the current recording if need to delete the current recording track. * For stopping the current recording if need to delete the current recording track.
*/ */
abstract protected TrackRecordingServiceConnection getTrackRecordingServiceConnection(); protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
return null;
}
/** /**
* Called after {@link TrackDeleteActivity} returns its result. * Called after {@link TrackDeleteActivity} returns its result.
*/ */
abstract protected void onTrackDeleted(); abstract protected void onTrackDeleted();
@Nullable
protected Track.Id getRecordingTrackId() {
return null;
}
} }
@@ -20,6 +20,7 @@ import android.app.SearchManager;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@@ -42,7 +43,6 @@ 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.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
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;
@@ -51,7 +51,7 @@ import de.dennisguse.opentracks.util.IntentUtils;
* *
* @author Leif Hendrik Wilden * @author Leif Hendrik Wilden
*/ */
public class MarkerListActivity extends AbstractActivity implements DeleteMarkerCaller, TrackRecordingServiceStatus.Listener { public class MarkerListActivity extends AbstractActivity implements DeleteMarkerCaller {
public static final String EXTRA_TRACK_ID = "track_id"; public static final String EXTRA_TRACK_ID = "track_id";
@@ -59,8 +59,8 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
private Track.Id recordingTrackId; private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private boolean recordingTrackPaused;
private Track track; private Track track;
private MarkerResourceCursorAdapter resourceCursorAdapter; private MarkerResourceCursorAdapter resourceCursorAdapter;
@@ -74,9 +74,13 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@Override @Override
public void run() { public void run() {
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) { if (service == null) {
service.addListener(MarkerListActivity.this); Log.w(TAG, "could not get TrackRecordingService");
return;
} }
service.getRecordingStatusObservable()
.observe(MarkerListActivity.this, status -> onRecordingStatusChanged(status));
} }
}; };
@@ -178,7 +182,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
insertMarkerMenuItem.setVisible(track != null && track.getId().equals(recordingTrackId) && !recordingTrackPaused); insertMarkerMenuItem.setVisible(track != null && track.getId().equals(recordingStatus.getTrackId()) && !recordingStatus.isPaused());
return super.onPrepareOptionsMenu(menu); return super.onPrepareOptionsMenu(menu);
} }
@@ -290,6 +294,10 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
// Do nothing // Do nothing
} }
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
recordingStatus = status;
}
private class MarkerLoaderCallback implements LoaderManager.LoaderCallbacks<Cursor> { private class MarkerLoaderCallback implements LoaderManager.LoaderCallbacks<Cursor> {
private String searchQuery = null; private String searchQuery = null;
@@ -344,14 +352,4 @@ 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;
}
} }
@@ -37,6 +37,8 @@ import de.dennisguse.opentracks.util.StringUtils;
* *
* @author Jimmy Shih * @author Jimmy Shih
*/ */
//TODO Subscribe to TrackRecordingService.recordingStatus
//TODO Could be a fragment
public class TrackController implements View.OnTouchListener { public class TrackController implements View.OnTouchListener {
private static final String TAG = TrackController.class.getSimpleName(); private static final String TAG = TrackController.class.getSimpleName();
@@ -54,17 +56,16 @@ public class TrackController implements View.OnTouchListener {
private final TrackControllerBinding viewBinding; private final TrackControllerBinding viewBinding;
private final TransitionDrawable transition; private final TransitionDrawable transition;
private boolean isRecording;
private boolean isRecordingPaused; private TrackRecordingService.RecordingStatus recordingStatus;
private Duration totalTime; private Duration totalTime;
private boolean isResumed = false;
private final Callback callback; private final Callback callback;
// A runnable to update the total time. // A runnable to update the total time.
private final Runnable updateTotalTimeRunnable = new Runnable() { private final Runnable updateTotalTimeRunnable = new Runnable() {
public void run() { public void run() {
if (isResumed && isRecording && !isRecordingPaused) { if (isResumed() && recordingStatus.isRecordingAndNotPaused()) {
updateTotalTime(); updateTotalTime();
setTotalTime(); setTotalTime();
handlerUpdateTotalTime.postDelayed(this, UI_UPDATE_INTERVAL.toMillis()); handlerUpdateTotalTime.postDelayed(this, UI_UPDATE_INTERVAL.toMillis());
@@ -84,7 +85,7 @@ public class TrackController implements View.OnTouchListener {
viewBinding.trackControllerRecord.setOnTouchListener(this); viewBinding.trackControllerRecord.setOnTouchListener(this);
viewBinding.trackControllerRecord.setOnClickListener((view) -> { viewBinding.trackControllerRecord.setOnClickListener((view) -> {
if (buttonDelay != null || (isRecording && !isRecordingPaused)) { if (buttonDelay != null || recordingStatus.isRecordingAndNotPaused()) {
return; return;
} }
@@ -120,7 +121,7 @@ public class TrackController implements View.OnTouchListener {
//To pause a recording //To pause a recording
if (viewBinding.trackControllerRecord.equals(view) if (viewBinding.trackControllerRecord.equals(view)
&& event.getAction() == MotionEvent.ACTION_DOWN && event.getAction() == MotionEvent.ACTION_DOWN
&& isRecording && !isRecordingPaused) { && recordingStatus.isRecordingAndNotPaused()) {
transition.startTransition(buttonDelayDuration); transition.startTransition(buttonDelayDuration);
@@ -143,7 +144,7 @@ public class TrackController implements View.OnTouchListener {
//To stop a recording //To stop a recording
if (viewBinding.trackControllerStop.equals(view) if (viewBinding.trackControllerStop.equals(view)
&& event.getAction() == MotionEvent.ACTION_DOWN && event.getAction() == MotionEvent.ACTION_DOWN
&& isRecording) { && recordingStatus.isRecording()) {
transition.startTransition(buttonDelayDuration); transition.startTransition(buttonDelayDuration);
@@ -165,13 +166,15 @@ public class TrackController implements View.OnTouchListener {
return false; return false;
} }
public void update(boolean recording, boolean paused) { public void update(TrackRecordingService.RecordingStatus recordingStatus) {
if (!isResumed) { if (!isResumed()) {
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable); handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
return; return;
} }
isRecording = recording;
isRecordingPaused = paused; this.recordingStatus = recordingStatus;
boolean isRecording = recordingStatus.isRecording();
boolean isRecordingPaused = recordingStatus.isPaused();
boolean visible = alwaysShow || isRecording; boolean visible = alwaysShow || isRecording;
viewBinding.trackControllerContainer.setVisibility(visible ? View.VISIBLE : View.GONE); viewBinding.trackControllerContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
@@ -197,19 +200,19 @@ public class TrackController implements View.OnTouchListener {
viewBinding.trackControllerTotalTime.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE); viewBinding.trackControllerTotalTime.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
if (isRecording) { if (recordingStatus.isRecording()) {
updateTotalTime(); updateTotalTime();
setTotalTime(); setTotalTime();
} }
} }
void onResume(boolean recording, boolean paused) { void onResume(TrackRecordingService.RecordingStatus recordingStatus) {
isResumed = true; this.recordingStatus = recordingStatus;
update(recording, paused); update(recordingStatus);
} }
void onPause() { void onPause() {
isResumed = false; recordingStatus = null;
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable); handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
} }
@@ -235,12 +238,18 @@ public class TrackController implements View.OnTouchListener {
} }
private void showStatusSetDefaultText() { private void showStatusSetDefaultText() {
viewBinding.trackControllerStatus.setText(isRecordingPaused ? R.string.generic_paused : R.string.generic_recording); viewBinding.trackControllerStatus.setText(recordingStatus.isPaused() ? R.string.generic_paused : R.string.generic_recording);
}
private boolean isResumed() {
return recordingStatus != null;
} }
public interface Callback { public interface Callback {
void recordStart(); void recordStart();
void recordPause(); void recordPause();
void recordStop(); void recordStop();
} }
} }
@@ -38,6 +38,7 @@ import android.view.View;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
@@ -55,7 +56,6 @@ import de.dennisguse.opentracks.databinding.TrackListBinding;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
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;
@@ -71,7 +71,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, TrackRecordingServiceStatus.Listener { public class TrackListActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackController.Callback {
private static final String TAG = TrackListActivity.class.getSimpleName(); private static final String TAG = TrackListActivity.class.getSimpleName();
@@ -80,7 +80,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private TrackRecordingServiceConnection trackRecordingServiceConnection; private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackController trackController; private TrackController trackController;
private ResourceCursorAdapter resourceCursorAdapter; private ResourceCursorAdapter resourceCursorAdapter;
private GpsStatusValue gpsStatusValue;
private TrackListBinding viewBinding; private TrackListBinding viewBinding;
@@ -89,7 +88,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Preferences // Preferences
private boolean metricUnits = true; private boolean metricUnits = true;
private boolean recordingTrackPaused = true; private GpsStatusValue gpsStatusValue = TrackRecordingService.STATUS_GPS_DEFAULT;
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
// 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() {
@@ -118,7 +118,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
runOnUiThread(() -> { runOnUiThread(() -> {
TrackListActivity.this.invalidateOptionsMenu(); TrackListActivity.this.invalidateOptionsMenu();
loaderCallbacks.restart(); loaderCallbacks.restart();
trackController.update(isRecording(), recordingTrackPaused); trackController.onResume(recordingStatus);
}); });
} }
} }
@@ -133,27 +133,29 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
@Override @Override
public void run() { public void run() {
// 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(recordingStatus));
TrackRecordingService 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.e(TAG, "service not available to start gps or a new recording");
gpsStatusValue = GpsStatusValue.GPS_NONE;
return; return;
} }
service.getRecordingStatusObservable()
.observe(TrackListActivity.this, status -> onRecordingStatusChanged(status));
// Get GPS status and listen GPS status changes. // Get GPS status and listen GPS status changes.
gpsStatusValue = service.getGpsStatus(); service.getGpsStatusObservable()
updateGpsMenuItem(true, isRecording()); .observe(TrackListActivity.this, status -> onGpsStatusChanged(status));
service.addListener(TrackListActivity.this);
if (isGpsStarted()) { updateGpsMenuItem(true, recordingStatus.isRecording());
if (service.getGpsStatusObservable().getValue().isGpsStarted()) {
return; return;
} }
//TODO Not cool to do this in a callback that might be called more than once!
service.tryStartGps(); service.tryStartGps();
gpsStatusValue = GpsStatusValue.GPS_ENABLED;
updateGpsMenuItem(true, isRecording());
} }
}; };
@@ -165,8 +167,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
gpsStatusValue = GpsStatusValue.GPS_NONE;
sharedPreferences = PreferencesUtils.getSharedPreferences(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback); trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
@@ -181,7 +181,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 (isRecording() && trackId == recordingTrackId.getId()) { if (recordingStatus.isRecording() && trackId == recordingStatus.getTrackId().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));
@@ -211,7 +211,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
int markerCountIndex = cursor.getColumnIndexOrThrow(TracksColumns.MARKER_COUNT); int markerCountIndex = cursor.getColumnIndexOrThrow(TracksColumns.MARKER_COUNT);
Track.Id trackId = new Track.Id(cursor.getLong(idIndex)); Track.Id trackId = new Track.Id(cursor.getLong(idIndex));
boolean isRecording = trackId.equals(recordingTrackId); boolean isRecording = trackId.equals(recordingStatus.getTrackId());
String icon = cursor.getString(iconIndex); String icon = cursor.getString(iconIndex);
int iconId = TrackIconUtils.getIconDrawable(icon); int iconId = TrackIconUtils.getIconDrawable(icon);
String name = cursor.getString(nameIndex); String name = cursor.getString(nameIndex);
@@ -222,7 +222,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex); String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
String description = cursor.getString(descriptionIndex); String description = cursor.getString(descriptionIndex);
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingTrackPaused, ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingStatus.isPaused(),
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount, iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
startTime, true, category, description, false); startTime, true, category, description, false);
} }
@@ -265,7 +265,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(isRecording(), recordingTrackPaused); trackController.onResume(recordingStatus);
} }
@Override @Override
@@ -306,7 +306,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
updateMenuItems(isGpsStarted(), isRecording()); updateMenuItems(gpsStatusValue.isGpsStarted(), recordingStatus.isRecording());
SearchView searchView = (SearchView) searchMenuItem.getActionView(); SearchView searchView = (SearchView) searchMenuItem.getActionView();
searchView.setQuery("", false); searchView.setQuery("", false);
@@ -322,7 +322,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
} else { } else {
// Invoke trackRecordingService // Invoke trackRecordingService
if (!isGpsStarted()) { if (!gpsStatusValue.isGpsStarted()) {
trackRecordingServiceConnection.startAndBind(this); trackRecordingServiceConnection.startAndBind(this);
bindChangedCallback.run(); bindChangedCallback.run();
} else { } else {
@@ -413,8 +413,10 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Do nothing // Do nothing
} }
private boolean isGpsStarted() { @Nullable
return gpsStatusValue != GpsStatusValue.GPS_NONE && gpsStatusValue != GpsStatusValue.GPS_DISABLED; @Override
protected Track.Id getRecordingTrackId() {
return recordingStatus.getTrackId();
} }
private void requestGPSPermissions() { private void requestGPSPermissions() {
@@ -554,16 +556,15 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
@Override @Override
public void recordStart() { public void recordStart() {
if (recordingTrackId == null) { if (recordingStatus.getTrackId() == 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);
startActivity(newIntent); startActivity(newIntent);
} else if (recordingTrackPaused) { } else if (recordingStatus.isPaused()) {
// Paused -> Resume // Paused -> Resume
updateMenuItems(false, true); updateMenuItems(false, true);
trackRecordingServiceConnection.resumeTrack(); trackRecordingServiceConnection.resumeTrack();
trackController.update(true, false);
} }
} }
@@ -571,7 +572,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
public void recordPause() { public void recordPause() {
updateMenuItems(false, true); updateMenuItems(false, true);
trackRecordingServiceConnection.pauseTrack(); trackRecordingServiceConnection.pauseTrack();
trackController.update(true, true);
} }
@Override @Override
@@ -580,29 +580,15 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
trackRecordingServiceConnection.stopRecording(TrackListActivity.this); trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
} }
private boolean isRecording() { public void onGpsStatusChanged(GpsStatusValue newStatus) {
return recordingTrackId != null;
}
@Override
public void onGpsStatus(GpsStatusValue newStatus) {
gpsStatusValue = newStatus; gpsStatusValue = newStatus;
updateGpsMenuItem(true, isRecording()); updateGpsMenuItem(true, recordingStatus.isRecording());
} }
@Override
public void onTrackRecordingPaused(boolean isPaused) {
if (recordingTrackPaused != isPaused) {
trackController.update(isRecording(), isPaused);
}
recordingTrackPaused = isPaused;
}
@Override private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
public void onTrackRecordingId(Track.Id trackId) { recordingStatus = status;
if (!isRecording() && trackId != null) {
trackController.update(true, recordingTrackPaused); trackController.update(recordingStatus);
}
recordingTrackId = trackId;
} }
} }
@@ -24,6 +24,7 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
@@ -42,7 +43,6 @@ import de.dennisguse.opentracks.fragments.IntervalsFragment;
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment; import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
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;
@@ -54,7 +54,7 @@ import de.dennisguse.opentracks.util.IntentUtils;
* @author Rodrigo Damazio * @author Rodrigo Damazio
*/ */
//TODO Should not use TrackRecordingServiceConnection; only used to determine if there is NO current recording, to enable resume functionality. //TODO Should not use TrackRecordingServiceConnection; only used to determine if there is NO current recording, to enable resume functionality.
public class TrackRecordedActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface, TrackRecordingServiceStatus.Listener { public class TrackRecordedActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface {
private static final String TAG = TrackRecordedActivity.class.getSimpleName(); private static final String TAG = TrackRecordedActivity.class.getSimpleName();
@@ -72,6 +72,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
private TrackRecordedBinding viewBinding; private TrackRecordedBinding viewBinding;
private Track.Id trackId; private Track.Id trackId;
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private TrackRecordingServiceConnection trackRecordingServiceConnection; private TrackRecordingServiceConnection trackRecordingServiceConnection;
@@ -79,9 +80,14 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
@Override @Override
public void run() { public void run() {
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) { if (service == null) {
service.addListener(TrackRecordedActivity.this); Log.w(TAG, "could not get TrackRecordingService");
return;
} }
service.getRecordingStatusObservable()
.observe(TrackRecordedActivity.this, status -> onRecordingStatusChanged(status));
} }
}; };
@@ -162,7 +168,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(recordingTrackId == null); menu.findItem(R.id.track_detail_resume_track).setVisible(!recordingStatus.isRecording());
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);
@@ -217,12 +223,13 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@Nullable
@Override @Override
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() { protected Track.Id getRecordingTrackId() {
// Not needed. return recordingStatus.getTrackId();
return null;
} }
@Override @Override
protected void onTrackDeleted() { protected void onTrackDeleted() {
runOnUiThread(this::finish); runOnUiThread(this::finish);
@@ -305,8 +312,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
startPostponedEnterTransition(); startPostponedEnterTransition();
} }
@Override private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
public void onTrackRecordingId(Track.Id trackId) { recordingStatus = status;
recordingTrackId = trackId;
} }
} }
@@ -28,7 +28,6 @@ import de.dennisguse.opentracks.fragments.IntervalsFragment;
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment; import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
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;
@@ -44,7 +43,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, TrackRecordingServiceStatus.Listener { public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface, TrackController.Callback {
public static final String EXTRA_TRACK_ID = "track_id"; public static final String EXTRA_TRACK_ID = "track_id";
@@ -62,20 +61,23 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
private TrackRecordingBinding viewBinding; private TrackRecordingBinding viewBinding;
// 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
@Deprecated
private Track.Id trackId; private Track.Id trackId;
private boolean recordingTrackPaused;
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private final Runnable bindChangedCallback = new Runnable() { private final Runnable bindChangedCallback = new Runnable() {
@Override @Override
public void run() { public void run() {
// After binding changes (is available), update the total time in trackController.
runOnUiThread(() -> trackController.update(true, recordingTrackPaused));
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) { if (service == null) {
Log.d(TAG, "could not get TrackRecordingService"); Log.w(TAG, "could not get TrackRecordingService");
return; return;
} }
service.getRecordingStatusObservable()
.observe(TrackRecordingActivity.this, status -> onRecordingStatusChanged(status));
if (!service.isRecording()) { if (!service.isRecording()) {
if (trackId == null) { if (trackId == null) {
// trackId isn't initialized -> leads a new recording. // trackId isn't initialized -> leads a new recording.
@@ -87,12 +89,10 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
// A recording track is on. // A recording track is on.
trackDataHub.loadTrack(trackId); trackDataHub.loadTrack(trackId);
trackDataHub.setRecordingTrackId(trackId); trackDataHub.setRecordingStatus(recordingStatus);
trackController.update(true, false); trackController.onResume(recordingStatus);
trackController.onResume(true, recordingTrackPaused);
} }
service.addListener(TrackRecordingActivity.this);
} }
}; };
@@ -109,10 +109,8 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
setFullscreenPolicy(); setFullscreenPolicy();
} }
if (key == null) return; if (key == null) return;
runOnUiThread(() -> {
TrackRecordingActivity.this.invalidateOptionsMenu(); runOnUiThread(TrackRecordingActivity.this::invalidateOptionsMenu);
trackController.update(true, recordingTrackPaused);
});
} }
}; };
@@ -207,13 +205,13 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
super.onResume(); super.onResume();
// Update UI // Update UI
this.invalidateOptionsMenu(); invalidateOptionsMenu();
if (trackId != null) { if (trackId != null) {
//TODO Pass recordingStatus directly to them
trackDataHub.loadTrack(trackId); trackDataHub.loadTrack(trackId);
trackDataHub.setRecordingTrackId(trackId); trackDataHub.setRecordingStatus(recordingStatus);
trackDataHub.setRecordingTrackPaused(recordingTrackPaused); trackController.onResume(recordingStatus);
trackController.onResume(true, recordingTrackPaused);
} }
/* /*
@@ -264,7 +262,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
updateMenuItems(recordingTrackPaused); updateMenuItems(recordingStatus.isPaused());
return super.onPrepareOptionsMenu(menu); return super.onPrepareOptionsMenu(menu);
} }
@@ -339,14 +337,12 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
public void recordStart() { public void recordStart() {
updateMenuItems(false); updateMenuItems(false);
trackRecordingServiceConnection.resumeTrack(); trackRecordingServiceConnection.resumeTrack();
trackController.update(true, false);
} }
@Override @Override
public void recordPause() { public void recordPause() {
updateMenuItems(true); updateMenuItems(true);
trackRecordingServiceConnection.pauseTrack(); trackRecordingServiceConnection.pauseTrack();
trackController.update(true, true);
} }
@Override @Override
@@ -404,24 +400,13 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
} }
} }
@Override private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
public void onTrackRecordingPaused(boolean isPaused) { recordingStatus = status;
if (recordingTrackPaused != isPaused) {
trackController.update(true, isPaused); trackController.update(recordingStatus);
trackDataHub.setRecordingTrackPaused(isPaused); trackDataHub.setRecordingStatus(recordingStatus);
}
recordingTrackPaused = isPaused;
setLockscreenPolicy(); setLockscreenPolicy();
setScreenOnPolicy(); setScreenOnPolicy();
} }
//TODO Why should this be interesting? This activity is only used for currently recording tracks.
@Override
public void onTrackRecordingId(Track.Id newTrackId) {
if (newTrackId != null && !newTrackId.equals(trackId)) {
trackId = newTrackId;
trackController.update(true, recordingTrackPaused);
trackDataHub.setRecordingTrackId(newTrackId);
}
}
} }
@@ -39,6 +39,7 @@ 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.TrackRecordingService;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater; import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
/** /**
@@ -76,12 +77,8 @@ public class TrackDataHub {
private Handler handler; private Handler handler;
private Track.Id selectedTrackId; private Track.Id selectedTrackId;
@Deprecated
//TODO Is there a better way to handle this? e.g., passing information rather from Activity than from Activity to TrackDataHub private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private Track.Id recordingTrackId;
@Deprecated
//TODO Is there a better way to handle this? e.g., passing information rather from Activity than from Activity to TrackDataHub
private boolean recordingTrackPaused;
// Track points sampling state // Track points sampling state
private int numLoadedPoints; private int numLoadedPoints;
@@ -208,14 +205,14 @@ public class TrackDataHub {
* 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); return selectedTrackId != null && selectedTrackId.equals(recordingStatus.getTrackId());
} }
/** /**
* Returns true if the selected track is paused. * Returns true if the selected track is paused.
*/ */
public boolean isSelectedTrackPaused() { public boolean isSelectedTrackPaused() {
return selectedTrackId != null && selectedTrackId.equals(recordingTrackId) && recordingTrackPaused; return selectedTrackId != null && selectedTrackId.equals(recordingStatus.getTrackId()) && recordingStatus.isPaused();
} }
/** /**
@@ -417,11 +414,7 @@ public class TrackDataHub {
lastSeenTrackPointId = null; lastSeenTrackPointId = null;
} }
public void setRecordingTrackId(Track.Id recordingTrackId) { public void setRecordingStatus(TrackRecordingService.RecordingStatus recordingStatus) {
this.recordingTrackId = recordingTrackId; this.recordingStatus = recordingStatus;
}
public void setRecordingTrackPaused(boolean recordingTrackPaused) {
this.recordingTrackPaused = recordingTrackPaused;
} }
} }
@@ -33,6 +33,8 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.core.app.TaskStackBuilder; import androidx.core.app.TaskStackBuilder;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
@@ -74,6 +76,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private static final String TAG = TrackRecordingService.class.getSimpleName(); private static final String TAG = TrackRecordingService.class.getSimpleName();
public static final RecordingStatus STATUS_DEFAULT = new RecordingStatus(null, false);
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
// The following variables are set in onCreate: // The following variables are set in onCreate:
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
private PeriodicTaskExecutor voiceExecutor; private PeriodicTaskExecutor voiceExecutor;
@@ -119,12 +124,18 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private HandlerServer handlerServer; private HandlerServer handlerServer;
private final TrackRecordingServiceStatus serviceStatus = new TrackRecordingServiceStatus(); private RecordingStatus recordingStatus;
private MutableLiveData<RecordingStatus> recordingStatusObservable;
private MutableLiveData<GpsStatusValue> gpsStatusObservable;
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
recordingStatusObservable = new MutableLiveData<>();
updateRecordingStatus(STATUS_DEFAULT);
gpsStatusObservable = new MutableLiveData<>(STATUS_GPS_DEFAULT);
handlerServer = new HandlerServer(this); handlerServer = new HandlerServer(this);
contentProviderUtils = new ContentProviderUtils(this); contentProviderUtils = new ContentProviderUtils(this);
@@ -149,8 +160,6 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
@Override @Override
public void onDestroy() { public void onDestroy() {
serviceStatus.onStop();
handlerServer.stop(this); handlerServer.stop(this);
handlerServer = null; handlerServer = null;
@@ -181,19 +190,22 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// This should be the next to last operation // This should be the next to last operation
wakeLock = SystemUtils.releaseWakeLock(wakeLock); wakeLock = SystemUtils.releaseWakeLock(wakeLock);
recordingStatusObservable = null;
gpsStatusObservable = null;
super.onDestroy(); super.onDestroy();
} }
public boolean isRecording() { public boolean isRecording() {
return serviceStatus.isRecording(); return recordingStatus.isRecording();
} }
public boolean isPaused() { public boolean isPaused() {
return serviceStatus.getRecordingTrackPaused(); return recordingStatus.isPaused();
} }
public Track.Id getRecordingTrackId() { public Track.Id getRecordingTrackId() {
return serviceStatus.getRecordingTrackId(); return recordingStatus.getTrackId();
} }
public TrackStatistics getTrackStatistics() { public TrackStatistics getTrackStatistics() {
@@ -223,14 +235,14 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
} }
if (name == null) { if (name == null) {
Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(serviceStatus.getRecordingTrackId()); Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(getRecordingTrackId());
if (nextMarkerNumber == null) { 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(serviceStatus.getRecordingTrackId()); TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(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;
@@ -244,7 +256,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
TrackStatistics stats = trackStatisticsUpdater.getTrackStatistics(); TrackStatistics stats = trackStatisticsUpdater.getTrackStatistics();
// Insert marker // Insert marker
Marker marker = new Marker(name, description, category, icon, serviceStatus.getRecordingTrackId(), stats, trackPoint, photoUrl); Marker marker = new Marker(name, description, category, icon, getRecordingTrackId(), stats, 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));
} }
@@ -266,7 +278,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
Track.Id trackId = new Track.Id(ContentUris.parseId(uri)); Track.Id trackId = new Track.Id(ContentUris.parseId(uri));
// Set recording status // Set recording status
serviceStatus.onChange(trackId, false); updateRecordingStatus(new RecordingStatus(trackId, false));
// Update database // Update database
track.setId(trackId); track.setId(trackId);
@@ -308,7 +320,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
insertTrackPoint(track, TrackPoint.createSegmentStartManual()); insertTrackPoint(track, TrackPoint.createSegmentStartManual());
// Set recording status // Set recording status
serviceStatus.onChange(trackId, false); updateRecordingStatus(new RecordingStatus(trackId, false));
startRecording(); startRecording();
} }
@@ -321,10 +333,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
} }
// Set recording status // Set recording status
serviceStatus.onChange(false); updateRecordingStatus(new RecordingStatus(getRecordingTrackId(), false));
// Update database // Update database
Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId()); Track track = contentProviderUtils.getTrack(getRecordingTrackId());
if (track != null) { if (track != null) {
insertTrackPoint(track, TrackPoint.createSegmentStartManual()); insertTrackPoint(track, TrackPoint.createSegmentStartManual());
} }
@@ -373,10 +385,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// Need to remember the recordingTrackId before setting it to null // Need to remember the recordingTrackId before setting it to null
boolean wasPause = isPaused(); boolean wasPause = isPaused();
Track.Id trackId = serviceStatus.getRecordingTrackId(); Track.Id trackId = getRecordingTrackId();
// Set recording status // Set recording status
serviceStatus.onChange(null, true); updateRecordingStatus(STATUS_DEFAULT);
if (!wasPause) { if (!wasPause) {
// Update database // Update database
@@ -404,10 +416,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
} }
// Set recording status // Set recording status
serviceStatus.onChange(true); updateRecordingStatus(new RecordingStatus(getRecordingTrackId(), true));
// Update database // Update database
Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId()); Track track = contentProviderUtils.getTrack(getRecordingTrackId());
if (track != null) { if (track != null) {
if (lastTrackPoint != null) { if (lastTrackPoint != null) {
insertTrackPointIfNewer(track, lastTrackPoint); insertTrackPointIfNewer(track, lastTrackPoint);
@@ -490,7 +502,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return; return;
} }
Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId()); Track track = contentProviderUtils.getTrack(getRecordingTrackId());
if (track == null) { if (track == null) {
Log.w(TAG, "Ignore newTrackPoint. No track."); Log.w(TAG, "Ignore newTrackPoint. No track.");
return; return;
@@ -564,17 +576,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
@Override @Override
public void newGpsStatus(GpsStatusValue gpsStatusValue) { public void newGpsStatus(GpsStatusValue gpsStatusValue) {
notificationManager.updateContent(getString(gpsStatusValue.message)); notificationManager.updateContent(getString(gpsStatusValue.message));
serviceStatus.onChange(gpsStatusValue);
}
public void addListener(@NonNull TrackRecordingServiceStatus.Listener listener) { gpsStatusObservable.postValue(gpsStatusValue);
serviceStatus.addListener(listener);
}
//TODO Check that this is used everywhere, where addListener is called!
//Otherwise, we keep objects in referenced and waste memory until this service instance is terminated.
public void removeListener(@NonNull TrackRecordingServiceStatus.Listener listener) {
serviceStatus.removeListener(listener);
} }
/** /**
@@ -666,7 +669,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, serviceStatus.getRecordingTrackId()); .putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, getRecordingTrackId());
PendingIntent pendingIntent = TaskStackBuilder.create(this) PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addParentStack(TrackRecordingActivity.class) .addParentStack(TrackRecordingActivity.class)
.addNextIntent(intent) .addNextIntent(intent)
@@ -702,8 +705,18 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
this.altitudeSumManager = altitudeSumManager; this.altitudeSumManager = altitudeSumManager;
} }
public GpsStatusValue getGpsStatus() { public LiveData<GpsStatusValue> getGpsStatusObservable() {
return handlerServer.getGpsStatus(); return gpsStatusObservable;
}
public LiveData<RecordingStatus> getRecordingStatusObservable() {
return recordingStatusObservable;
}
private void updateRecordingStatus(RecordingStatus status) {
Log.i(TAG, "new status " + recordingStatus + " -> " + status);
recordingStatus = status;
recordingStatusObservable.postValue(recordingStatus);
} }
@Override @Override
@@ -717,6 +730,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
} }
} }
public class Binder extends android.os.Binder { public class Binder extends android.os.Binder {
private Binder() { private Binder() {
@@ -727,4 +741,51 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return TrackRecordingService.this; return TrackRecordingService.this;
} }
} }
public static class RecordingStatus {
private final Track.Id trackId;
private final boolean paused;
public RecordingStatus(Track.Id trackId, boolean paused) {
this.trackId = trackId;
this.paused = paused;
}
public Track.Id getTrackId() {
return trackId;
}
public boolean isRecording() {
return trackId != null;
}
public boolean isPaused() {
return paused;
}
public boolean isRecordingAndNotPaused() {
return isRecording() && !isPaused();
}
public RecordingStatus pause() {
return new RecordingStatus(getTrackId(), true);
}
public RecordingStatus record(@NonNull Track.Id track) {
return new RecordingStatus(trackId, false);
}
public RecordingStatus stop() {
return STATUS_DEFAULT;
}
@Override
public String toString() {
return "RecordingStatus{" +
"trackId=" + trackId +
", paused=" + paused +
'}';
}
}
} }
@@ -1,83 +0,0 @@
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<>();
void addListener(@NonNull Listener listener) {
if (this.listeners.contains(listener)) {
return;
}
this.listeners.add(listener);
listener.onTrackRecordingId(recordingTrackId);
listener.onTrackRecordingPaused(recordingTrackPaused);
}
void removeListener(@NonNull Listener listener) {
this.listeners.remove(listener);
}
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) {}
}
}
@@ -20,4 +20,8 @@ public enum GpsStatusValue {
this.icon = icon; this.icon = icon;
this.message = message; this.message = message;
} }
public boolean isGpsStarted() {
return this != GpsStatusValue.GPS_NONE && this != GpsStatusValue.GPS_DISABLED;
}
} }
@@ -20,12 +20,10 @@ 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.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
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;
@@ -93,7 +91,7 @@ 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 implements TrackRecordingServiceStatus.Listener { public static class PrefsFragment extends PreferenceFragmentCompat {
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
@@ -107,15 +105,20 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
}; };
private TrackRecordingServiceConnection trackRecordingServiceConnection; private TrackRecordingServiceConnection trackRecordingServiceConnection;
private boolean isRecording; private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private final Runnable bindServiceCallback = new Runnable() { private final Runnable bindServiceCallback = new Runnable() {
@Override @Override
public void run() { public void run() {
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) { if (service == null) {
service.addListener(PrefsFragment.this); Log.w(TAG, "could not get TrackRecordingService");
return;
} }
service.getRecordingStatusObservable()
.observe(PrefsFragment.this, status -> onRecordingStatusChanged(status));
} }
}; };
@@ -218,6 +221,7 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
Preference importPreference = findPreference(getString(R.string.settings_import)); Preference importPreference = findPreference(getString(R.string.settings_import));
Preference exportPreference = findPreference(getString(R.string.settings_export)); Preference exportPreference = findPreference(getString(R.string.settings_export));
boolean isRecording = recordingStatus.isRecording();
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) : ""); importPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
exportPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : ""); exportPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
@@ -292,10 +296,9 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
}); });
} }
@Override private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
public void onTrackRecordingId(Track.Id trackId) { this.recordingStatus = status;
if (trackId != null && !isRecording && isAdded()) { if (!status.isRecording() && isAdded()) {
isRecording = true;
updatePrefsDependOnRecording(); updatePrefsDependOnRecording();
} }
} }