Track recording service handles the status of recording: track's id, pause and GPS status.

Created an interface for all components that want to know about recording status through the service.
All interface's methods are optional so every component can implement what they need.
Fixes #388.
This commit is contained in:
Román Martínez
2021-03-23 20:08:25 +01:00
parent f3cf1a2571
commit e09b04308c
26 changed files with 426 additions and 466 deletions
@@ -37,8 +37,6 @@ import androidx.core.app.TaskStackBuilder;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackListActivity;
@@ -48,7 +46,6 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.io.file.exporter.ExportServiceResultReceiver;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
@@ -83,23 +80,13 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private TrackRecordingServiceNotificationManager notificationManager;
private SharedPreferences sharedPreferences;
private Track.Id recordingTrackId;
private boolean recordingTrackPaused;
private int recordingDistanceInterval;
private int maxRecordingDistance;
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Context context = TrackRecordingService.this;
if (PreferencesUtils.isKey(TrackRecordingService.this, R.string.recording_track_id_key, key)) {
// Only through the TrackRecordingService can one stop a recording and set the recordingTrackId to -1L.
if (PreferencesUtils.isRecording(sharedPreferences, TrackRecordingService.this)) {
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, TrackRecordingService.this);
}
}
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, context);
}
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
voiceExecutor.setMetricUnits(metricUnits);
@@ -132,7 +119,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private HandlerServer handlerServer;
private List<TrackRecordingServiceCallback> listeners = new ArrayList<>();
private final TrackRecordingServiceStatus serviceStatus = new TrackRecordingServiceStatus();
@Override
public void onCreate() {
@@ -145,14 +132,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
notificationManager = new TrackRecordingServiceNotificationManager(this);
// onSharedPreferenceChanged might not set recordingTrackId.
recordingTrackId = null;
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
restartTrackAfterServiceRestart();
}
@Override
@@ -167,13 +149,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
@Override
public void onDestroy() {
if (listeners != null) {
for (TrackRecordingServiceCallback listener : listeners) {
listener.onGpsStatusChange(GpsStatusValue.GPS_NONE);
}
listeners.clear();
listeners = null;
}
serviceStatus.onStop();
handlerServer.stop(this);
handlerServer = null;
@@ -212,15 +188,11 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
}
public boolean isRecording() {
return PreferencesUtils.isRecording(recordingTrackId);
return serviceStatus.isRecording();
}
public boolean isPaused() {
return recordingTrackPaused;
}
public Track.Id getRecordingTrackId() {
return recordingTrackId;
return serviceStatus.getRecordingTrackPaused();
}
public TrackStatistics getTrackStatistics() {
@@ -250,14 +222,14 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
}
if (name == null) {
int nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(recordingTrackId);
if (nextMarkerNumber == -1) {
Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(serviceStatus.getRecordingTrackId());
if (nextMarkerNumber == null) {
nextMarkerNumber = 1;
}
name = getString(R.string.marker_name_format, nextMarkerNumber + 1);
}
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(recordingTrackId);
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(serviceStatus.getRecordingTrackId());
if (trackPoint == null) {
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
return null;
@@ -273,7 +245,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
long duration = stats.getTotalTime().toMillis();
// Insert marker
Marker marker = new Marker(name, description, category, icon, recordingTrackId, length, duration, trackPoint, photoUrl);
Marker marker = new Marker(name, description, category, icon, serviceStatus.getRecordingTrackId(), length, duration, trackPoint, photoUrl);
Uri uri = contentProviderUtils.insertMarker(marker);
return new Marker.Id(ContentUris.parseId(uri));
}
@@ -294,8 +266,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
Uri uri = contentProviderUtils.insertTrack(track);
Track.Id trackId = new Track.Id(ContentUris.parseId(uri));
// Update shared preferences
updateRecordingState(trackId, false);
// Set recording status
serviceStatus.onChange(trackId, false);
// Update database
track.setId(trackId);
@@ -336,48 +308,23 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
insertTrackPoint(track, TrackPoint.createSegmentStartManual());
// Update shared preferences.
updateRecordingState(trackId, false);
// Set recording status
serviceStatus.onChange(trackId, false);
startRecording();
}
/**
* Try to restart the previous recording track in case the service has been restarted by the system, which can sometimes happen.
*/
private void restartTrackAfterServiceRestart() {
Track track = contentProviderUtils.getTrack(recordingTrackId);
if (track == null) {
if (isRecording()) {
Log.w(TAG, "track is null, but recordingTrackId not -1L. " + recordingTrackId.getId());
updateRecordingState(null, true);
}
showNotification(false);
return;
}
Log.d(TAG, "Restarting track: " + track.getId());
trackStatisticsUpdater = new TrackStatisticsUpdater();
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
trackStatisticsUpdater.addTrackPoint(trackPointIterator, recordingDistanceInterval);
} catch (RuntimeException e) {
Log.e(TAG, "RuntimeException", e);
}
startRecording();
}
void resumeCurrentTrack() {
if (!isRecording() || !isPaused()) {
Log.d(TAG, "Ignore resumeCurrentTrack. Not recording or not paused.");
return;
}
updateRecordingState(recordingTrackId, false);
// Set recording status
serviceStatus.onChange(false);
// Update database
Track track = contentProviderUtils.getTrack(recordingTrackId);
Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId());
if (track != null) {
insertTrackPoint(track, TrackPoint.createSegmentStartManual());
}
@@ -417,17 +364,18 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
showNotification(true);
}
void endCurrentTrack() {
Track.Id endCurrentTrack() {
if (!isRecording()) {
Log.d(TAG, "Ignore endCurrentTrack. Not recording.");
return;
return null;
}
// Need to remember the recordingTrackId before setting it to -1L
// Need to remember the recordingTrackId before setting it to null
boolean wasPause = isPaused();
Track.Id trackId = recordingTrackId;
Track.Id trackId = serviceStatus.getRecordingTrackId();
updateRecordingState(null, true);
// Set recording status
serviceStatus.onChange(null, true);
if (!wasPause) {
// Update database
@@ -445,6 +393,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
ExportUtils.postWorkoutExport(this, track, new ExportServiceResultReceiver(new Handler(), this));
endRecording(true);
return trackId;
}
void pauseCurrentTrack() {
@@ -453,10 +403,11 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return;
}
updateRecordingState(recordingTrackId, true);
// Set recording status
serviceStatus.onChange(true);
// Update database
Track track = contentProviderUtils.getTrack(recordingTrackId);
Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId());
if (track != null) {
if (lastTrackPoint != null) {
insertTrackPointIfNewer(track, lastTrackPoint);
@@ -528,21 +479,6 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return lastTrackPoint != null;
}
/**
* Updates the recording states.
* This will inform subscribed {@link OnSharedPreferenceChangeListener}.
*
* @param trackId the recording track id
* @param paused true if the recording is paused
*/
private void updateRecordingState(Track.Id trackId, boolean paused) {
recordingTrackId = trackId;
long currentTrackId = trackId != null ? trackId.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
PreferencesUtils.setLong(sharedPreferences, this, R.string.recording_track_id_key, currentTrackId);
recordingTrackPaused = paused;
PreferencesUtils.setBoolean(sharedPreferences, this, R.string.recording_track_paused_key, recordingTrackPaused);
}
@Override
public void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy) {
if (!isRecording() || isPaused()) {
@@ -550,7 +486,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return;
}
Track track = contentProviderUtils.getTrack(recordingTrackId);
Track track = contentProviderUtils.getTrack(serviceStatus.getRecordingTrackId());
if (track == null) {
Log.w(TAG, "Ignore newTrackPoint. No track.");
return;
@@ -623,16 +559,12 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
@Override
public void newGpsStatus(GpsStatusValue gpsStatusValue) {
if (listeners != null) {
notificationManager.updateContent(getString(gpsStatusValue.message));
for (TrackRecordingServiceCallback listener : listeners) {
listener.onGpsStatusChange(gpsStatusValue);
}
}
notificationManager.updateContent(getString(gpsStatusValue.message));
serviceStatus.onChange(gpsStatusValue);
}
public void addListener(TrackRecordingServiceCallback listener) {
listeners.add(listener);
public void addListener(@NonNull TrackRecordingServiceStatus.Listener listener) {
serviceStatus.addListener(listener);
}
/**
@@ -724,7 +656,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private void showNotification(boolean isGpsStarted) {
if (isRecording()) {
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, recordingTrackId);
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, serviceStatus.getRecordingTrackId());
PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addParentStack(TrackRecordingActivity.class)
.addNextIntent(intent)
@@ -1,5 +1,6 @@
package de.dennisguse.opentracks.services;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.time.Duration;
@@ -28,7 +29,7 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
}
@Override
public void addListener(TrackRecordingServiceCallback listener) {
public void addListener(@NonNull TrackRecordingServiceStatus.Listener listener) {
trackRecordingService.addListener(listener);
}
@@ -67,8 +68,8 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
}
@Override
public void endCurrentTrack() {
trackRecordingService.endCurrentTrack();
public Track.Id endCurrentTrack() {
return trackRecordingService.endCurrentTrack();
}
@Override
@@ -81,11 +82,6 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
return trackRecordingService.isPaused();
}
@Override
public Track.Id getRecordingTrackId() {
return trackRecordingService.getRecordingTrackId();
}
@Override
public Duration getTotalTime() {
return trackRecordingService.getTotalTime();
@@ -1,10 +0,0 @@
package de.dennisguse.opentracks.services;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
/**
* Interface all activities have to implements to receive information from the service.
*/
public interface TrackRecordingServiceCallback {
void onGpsStatusChange(GpsStatusValue newStatus);
}
@@ -20,7 +20,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
@@ -31,11 +30,7 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackEditActivity;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Wrapper for the track recording service.
@@ -64,6 +59,13 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
this.callback = callback;
}
public void bind(@NonNull Context context) {
if (trackRecordingService != null) {
return;
}
context.bindService(new Intent(context, TrackRecordingService.class), this, 0);
}
/**
* Starts and binds the service.
*/
@@ -202,30 +204,12 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
return null;
}
public void stopRecording(@NonNull Context context, boolean showEditor) {
public void stopRecording(@NonNull Context context) {
TrackRecordingServiceInterface trackRecordingService = getServiceIfBound();
if (trackRecordingService == null) {
Log.e(TAG, "TrackRecordingService not connected.");
} else {
try {
if (showEditor) {
// Need to remember the recordingTrackId before calling endCurrentTrack() as endCurrentTrack() sets the value to -1L.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, context);
trackRecordingService.endCurrentTrack();
if (PreferencesUtils.isRecording(sharedPreferences, context)) {
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId)
.putExtra(TrackEditActivity.EXTRA_NEW_TRACK, true);
context.startActivity(intent);
}
} else {
trackRecordingService.endCurrentTrack();
}
} catch (Exception e) {
//TODO What exception are we catching here? Should be removed...
Log.e(TAG, "Unable to stop recording.", e);
}
trackRecordingService.endCurrentTrack();
}
unbindAndStop(context);
}
@@ -15,6 +15,7 @@
*/
package de.dennisguse.opentracks.services;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.time.Duration;
@@ -44,7 +45,12 @@ public interface TrackRecordingServiceInterface {
void resumeTrack(Track.Id trackId);
void endCurrentTrack();
/**
* End current track.
*
* @return the Track.Id of the track that is ended.
*/
Track.Id endCurrentTrack();
boolean isRecording();
@@ -53,11 +59,6 @@ public interface TrackRecordingServiceInterface {
*/
boolean isPaused();
/**
* Gets the current recording track ID. Returns null if not recording.
*/
Track.Id getRecordingTrackId();
/**
* Gets the total time for the current recording track. Returns 0 if not recording.
*/
@@ -100,7 +101,7 @@ public interface TrackRecordingServiceInterface {
@VisibleForTesting
void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy);
void addListener(TrackRecordingServiceCallback listener);
void addListener(@NonNull TrackRecordingServiceStatus.Listener listener);
GpsStatusValue getGpsStatus();
}
@@ -0,0 +1,79 @@
package de.dennisguse.opentracks.services;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
/**
* This class handles the status of the recording service.
* Also offer an interface through which to know the recording Track.Id, pause status and GPS status.
*/
public class TrackRecordingServiceStatus {
private Track.Id recordingTrackId;
private boolean recordingTrackPaused;
private final List<Listener> listeners = new ArrayList<>();
public void addListener(@NonNull Listener listener) {
if (this.listeners.contains(listener)) {
return;
}
this.listeners.add(listener);
listener.onTrackRecordingId(recordingTrackId);
listener.onTrackRecordingPaused(recordingTrackPaused);
}
public boolean getRecordingTrackPaused() {
return this.recordingTrackPaused;
}
public Track.Id getRecordingTrackId() {
return this.recordingTrackId;
}
public void onStop() {
for (Listener listener : listeners) {
listener.onGpsStatus(GpsStatusValue.GPS_NONE);
listener.onTrackRecordingId(null);
listener.onTrackRecordingPaused(true);
}
listeners.clear();
}
public boolean isRecording() {
return recordingTrackId != null;
}
void onChange(Track.Id trackId, boolean paused) {
recordingTrackId = trackId;
recordingTrackPaused = paused;
for (Listener listener : listeners) {
listener.onTrackRecordingId(recordingTrackId);
listener.onTrackRecordingPaused(recordingTrackPaused);
}
}
void onChange(boolean paused) {
recordingTrackPaused = paused;
for (Listener listener : listeners) {
listener.onTrackRecordingPaused(recordingTrackPaused);
}
}
void onChange(GpsStatusValue statusValue) {
for (Listener listener : listeners) {
listener.onGpsStatus(statusValue);
}
}
public interface Listener {
default void onGpsStatus(GpsStatusValue newValue) {}
default void onTrackRecordingPaused(boolean paused) {}
default void onTrackRecordingId(Track.Id trackId) {}
}
}
@@ -31,6 +31,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.AnnouncementUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -57,6 +58,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
private final ContentProviderUtils contentProviderUtils;
private Track.Id recordingTrackId;
private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
@@ -139,6 +142,12 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
Log.e(TAG, "TrackRecordingService is null.");
return;
}
trackRecordingService.addListener(new TrackRecordingServiceStatus.Listener() {
@Override
public void onTrackRecordingId(Track.Id trackId) {
recordingTrackId = trackId;
}
});
announce(trackRecordingService.getTrackStatistics());
}
@@ -171,7 +180,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
return;
}
Track track = contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
Track track = contentProviderUtils.getTrack(recordingTrackId);
String category = track != null ? track.getCategory() : "";
//TODO Querying all TrackPoints all the time is inefficient; use TrackDataHub or something else.