Refactoring TrackRecordingServiceConnection
This commit is contained in:
Dennis Guse
2023-12-27 11:06:35 +01:00
committed by GitHub
13 changed files with 79 additions and 160 deletions
+1
View File
@@ -133,6 +133,7 @@ dependencies {
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'com.google.android.material:material:1.11.0' implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.core:core:1.12.0'
implementation 'androidx.core:core-splashscreen:1.0.1' implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'androidx.mediarouter:mediarouter:1.6.0' implementation 'androidx.mediarouter:mediarouter:1.6.0'
@@ -28,7 +28,6 @@ import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.databinding.TrackEditBinding; import de.dennisguse.opentracks.databinding.TrackEditBinding;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.TrackUtils; import de.dennisguse.opentracks.util.TrackUtils;
/** /**
@@ -44,7 +43,6 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
private static final String ICON_VALUE_KEY = "icon_value_key"; private static final String ICON_VALUE_KEY = "icon_value_key";
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
private Track track; private Track track;
private ActivityType activityType; private ActivityType activityType;
@@ -55,7 +53,6 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
protected void onCreate(Bundle bundle) { protected void onCreate(Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
trackRecordingServiceConnection = new TrackRecordingServiceConnection();
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID); Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
if (trackId == null) { if (trackId == null) {
Log.e(TAG, "invalid trackId"); Log.e(TAG, "invalid trackId");
@@ -114,18 +111,6 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar); setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
} }
@Override
protected void onStart() {
super.onStart();
trackRecordingServiceConnection.startConnection(this);
}
@Override
protected void onStop() {
super.onStop();
trackRecordingServiceConnection.unbind(this);
}
@Override @Override
public void onSaveInstanceState(@NonNull Bundle outState) { public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
@@ -157,14 +157,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
} else { } else {
if (gpsStatusValue.isGpsStarted()) { if (gpsStatusValue.isGpsStarted()) {
recordingStatusConnection.unbindAndStop(this); recordingStatusConnection.stopService(this);
recordingStatusConnection.startConnection(this); //TODO We need to stay listening!
} else { } else {
new TrackRecordingServiceConnection((service, connection) -> { TrackRecordingServiceConnection.execute(this, (service, connection) -> service.tryStartSensors());
service.tryStartSensors();
connection.unbind(this);
}).startAndBindWithCallback(this);
} }
} }
}); });
@@ -183,15 +178,13 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
// Not Recording -> Recording // Not Recording -> Recording
Log.i(TAG, "Starting recording"); Log.i(TAG, "Starting recording");
updateGpsMenuItem(false, true); updateGpsMenuItem(false, true);
new TrackRecordingServiceConnection((service, connection) -> { TrackRecordingServiceConnection.execute(this, (service, connection) -> {
Track.Id trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class); Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
newIntent.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId); newIntent.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
startActivity(newIntent); startActivity(newIntent);
});
connection.unbind(this);
}).startAndBind(this, true);
}); });
viewBinding.trackListFabAction.setOnLongClickListener((view) -> { viewBinding.trackListFabAction.setOnLongClickListener((view) -> {
if (!recordingStatus.isRecording()) { if (!recordingStatus.isRecording()) {
@@ -220,7 +213,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
super.onStart(); super.onStart();
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
recordingStatusConnection.startConnection(this); recordingStatusConnection.bind(this);
} }
@Override @Override
@@ -201,7 +201,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
} }
if (item.getItemId() == R.id.track_detail_resume_track) { if (item.getItemId() == R.id.track_detail_resume_track) {
new TrackRecordingServiceConnection((service, connection) -> { TrackRecordingServiceConnection.execute(this, (service, connection) -> {
service.resumeTrack(trackId); service.resumeTrack(trackId);
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class) Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
@@ -209,9 +209,8 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
startActivity(newIntent); startActivity(newIntent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
connection.unbind(this);
finish(); finish();
}).startAndBind(this, true); });
return true; return true;
} }
@@ -207,7 +207,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.startConnection(this); trackRecordingServiceConnection.bind(this);
trackDataHub.start(); trackDataHub.start();
} }
@@ -224,7 +224,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
trackDataHub.setRecordingStatus(recordingStatus); trackDataHub.setRecordingStatus(recordingStatus);
} }
trackRecordingServiceConnection.startAndBindWithCallback(this); trackRecordingServiceConnection.bind(this);
} }
@Override @Override
@@ -136,7 +136,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
} }
private void resumeTrackAndFinish() { private void resumeTrackAndFinish() {
new TrackRecordingServiceConnection((service, connection) -> { TrackRecordingServiceConnection.execute(this, (service, connection) -> {
service.resumeTrack(trackId); service.resumeTrack(trackId);
Intent newIntent = IntentUtils.newIntent(TrackStoppedActivity.this, TrackRecordingActivity.class) Intent newIntent = IntentUtils.newIntent(TrackStoppedActivity.this, TrackRecordingActivity.class)
@@ -144,9 +144,8 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
startActivity(newIntent); startActivity(newIntent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
connection.unbind(this);
finish(); finish();
}).startAndBind(this, true); });
} }
@Override @Override
@@ -88,7 +88,7 @@ public class StatisticsRecordingFragment extends Fragment {
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.startConnection(getContext()); trackRecordingServiceConnection.bind(getContext());
} }
@Override @Override
@@ -37,8 +37,7 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
if (PreferencesUtils.isPublicAPIenabled()) { if (PreferencesUtils.isPublicAPIenabled()) {
Log.i(TAG, "Received and trying to execute requested action."); Log.i(TAG, "Received and trying to execute requested action.");
new TrackRecordingServiceConnection(serviceConnectedCallback) TrackRecordingServiceConnection.execute(this, serviceConnectedCallback);
.startAndBind(this, isStartServiceForeground());
} else { } else {
Toast.makeText(this, getString(R.string.settings_public_api_disabled_toast), Toast.LENGTH_LONG).show(); Toast.makeText(this, getString(R.string.settings_public_api_disabled_toast), Toast.LENGTH_LONG).show();
Log.w(TAG, "Public API is disabled; ignoring request."); Log.w(TAG, "Public API is disabled; ignoring request.");
@@ -46,10 +45,6 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
} }
} }
protected boolean isStartServiceForeground() {
return false;
}
protected abstract void execute(TrackRecordingService service); protected abstract void execute(TrackRecordingService service);
protected abstract boolean isPostExecuteStopService(); protected abstract boolean isPostExecuteStopService();
@@ -62,9 +62,4 @@ public class StartRecording extends AbstractAPIActivity {
protected boolean isPostExecuteStopService() { protected boolean isPostExecuteStopService() {
return false; return false;
} }
@Override
protected boolean isStartServiceForeground() {
return true;
}
} }
@@ -30,6 +30,7 @@ import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.core.app.ServiceCompat;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
@@ -111,7 +112,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
recordingDataObservable = new MutableLiveData<>(NOT_RECORDING); recordingDataObservable = new MutableLiveData<>(NOT_RECORDING);
trackPointCreator = new TrackPointCreator(this); trackPointCreator = new TrackPointCreator(this);
trackRecordingManager = new TrackRecordingManager(this, trackPointCreator, this , handler); trackRecordingManager = new TrackRecordingManager(this, trackPointCreator, this, handler);
voiceAnnouncementManager = new VoiceAnnouncementManager(this); voiceAnnouncementManager = new VoiceAnnouncementManager(this);
notificationManager = new TrackRecordingServiceNotificationManager(this); notificationManager = new TrackRecordingServiceNotificationManager(this);
@@ -213,19 +214,15 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
Log.i(TAG, "startSensors"); Log.i(TAG, "startSensors");
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock); wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
trackPointCreator.start(this, handler); trackPointCreator.start(this, handler);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
if (!PermissionRequester.RECORDING.hasPermission(this)) { if (!PermissionRequester.RECORDING.hasPermission(this)) {
Toast.makeText(this, R.string.permission_recording_failed, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.permission_recording_failed, Toast.LENGTH_LONG).show();
return; return;
}
} }
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION + ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
} else {
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this));
} }
ServiceCompat.startForeground(this, TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION + ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
} }
public void endCurrentTrack() { public void endCurrentTrack() {
@@ -42,7 +42,7 @@ import de.dennisguse.opentracks.data.models.Marker;
* *
* @author Rodrigo Damazio * @author Rodrigo Damazio
*/ */
public class TrackRecordingServiceConnection implements ServiceConnection, DeathRecipient { public class TrackRecordingServiceConnection {
private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName(); private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName();
@@ -50,9 +50,29 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
private TrackRecordingService trackRecordingService; private TrackRecordingService trackRecordingService;
public TrackRecordingServiceConnection() { private final ServiceConnection serviceConnection = new ServiceConnection() {
callback = null; @Override
} public void onServiceConnected(ComponentName className, IBinder service) {
Log.i(TAG, "Connected to the service: " + service);
try {
service.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
Log.e(TAG, "Failed to bind a death recipient.", e);
}
setTrackRecordingService(((TrackRecordingService.Binder) service).getService());
}
@Override
public void onServiceDisconnected(ComponentName className) {
Log.i(TAG, "Disconnected from the service.");
setTrackRecordingService(null);
}
};
private final DeathRecipient deathRecipient = () -> {
Log.d(TAG, "Service died.");
setTrackRecordingService(null);
};
public TrackRecordingServiceConnection(@NonNull Callback callback) { public TrackRecordingServiceConnection(@NonNull Callback callback) {
this.callback = callback; this.callback = callback;
@@ -60,58 +80,13 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
public void bind(@NonNull Context context) { public void bind(@NonNull Context context) {
if (trackRecordingService != null) { if (trackRecordingService != null) {
return;
}
context.bindService(new Intent(context, TrackRecordingService.class), this, 0);
}
/**
* Starts and binds the service.
*
* @param foreground is the service expected to call `startForeground()`?
*/
public void startAndBind(Context context, boolean foreground) {
if (trackRecordingService != null) {
// Service is already started and bound.
return;
}
Log.i(TAG, "Starting the service.");
if (foreground) {
ContextCompat.startForegroundService(context, new Intent(context, TrackRecordingService.class));
} else {
context.startService(new Intent(context, TrackRecordingService.class));
}
startConnection(context);
}
//TODO There should be a better way to implement this.
/**
* Triggers the onConnected() callback even if already connected.
*/
//TODO Check if this is actually needed as it is used to re-connect from Activities in onResume by using a LiveData; might be obsolete. If not, there should be a better way to implement this.
@Deprecated
public void startAndBindWithCallback(Context context) {
if (trackRecordingService == null) {
startAndBind(context, false);
return;
}
if (callback != null) {
callback.onConnected(trackRecordingService, this); callback.onConnected(trackRecordingService, this);
}
}
public void startConnection(@NonNull Context context) {
if (trackRecordingService != null) {
// Service is already started and bound.
return; return;
} }
Log.i(TAG, "Binding the service."); Log.i(TAG, "Binding the service.");
int flags = BuildConfig.DEBUG ? Context.BIND_DEBUG_UNBIND : 0; int flags = BuildConfig.DEBUG ? Context.BIND_DEBUG_UNBIND : 0;
context.bindService(new Intent(context, TrackRecordingService.class), this, flags); context.bindService(new Intent(context, TrackRecordingService.class), serviceConnection, flags);
} }
/** /**
@@ -120,72 +95,45 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
//TODO This is often called for one-shot operations and should be refactored as unbinding is required. //TODO This is often called for one-shot operations and should be refactored as unbinding is required.
public void unbind(Context context) { public void unbind(Context context) {
try { try {
context.unbindService(this); context.unbindService(serviceConnection);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// Means not bound to the service. OK to ignore. // Means not bound to the service. OK to ignore.
} }
setTrackRecordingService(null); setTrackRecordingService(null);
} }
public void unbindAndStop(Context context) { public void stopService(Context context) {
unbind(context);
context.stopService(new Intent(context, TrackRecordingService.class)); context.stopService(new Intent(context, TrackRecordingService.class));
} }
@Nullable public void unbindAndStop(Context context) {
public TrackRecordingService getServiceIfBound() { unbind(context);
return trackRecordingService; stopService(context);
} }
private void setTrackRecordingService(TrackRecordingService value) { private void setTrackRecordingService(TrackRecordingService value) {
trackRecordingService = value; trackRecordingService = value;
if (callback != null) { if (value != null) {
if (value != null) { callback.onConnected(value, this);
callback.onConnected(value, this);
} else {
callback.onDisconnected();
}
} }
} }
@Override //TODO Move to some other place; not needed here.
public void onServiceConnected(ComponentName className, IBinder service) {
Log.i(TAG, "Connected to the service: " + service);
try {
service.linkToDeath(this, 0);
} catch (RemoteException e) {
Log.e(TAG, "Failed to bind a death recipient.", e);
}
setTrackRecordingService(((TrackRecordingService.Binder) service).getService());
}
@Override
public void onServiceDisconnected(ComponentName className) {
Log.i(TAG, "Disconnected from the service.");
setTrackRecordingService(null);
}
@Override
public void binderDied() {
Log.d(TAG, "Service died.");
setTrackRecordingService(null);
}
@Nullable @Nullable
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) {
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 { return null;
try { }
Marker.Id marker = trackRecordingService.insertMarker(name, category, description, photoUrl);
if (marker != null) { try {
Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show(); Marker.Id marker = trackRecordingService.insertMarker(name, category, description, photoUrl);
return marker; if (marker != null) {
} Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show();
} catch (IllegalStateException e) { return marker;
Log.e(TAG, "Unable to add marker.", e);
} }
} catch (IllegalStateException e) {
Log.e(TAG, "Unable to add marker.", e);
} }
Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show(); Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show();
@@ -193,7 +141,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
} }
public void stopRecording(@NonNull Context context) { public void stopRecording(@NonNull Context context) {
TrackRecordingService trackRecordingService = getServiceIfBound();
if (trackRecordingService == null) { if (trackRecordingService == null) {
Log.e(TAG, "TrackRecordingService not connected."); Log.e(TAG, "TrackRecordingService not connected.");
} else { } else {
@@ -203,9 +150,17 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
} }
public interface Callback { public interface Callback {
void onConnected(TrackRecordingService service, TrackRecordingServiceConnection connection); void onConnected(TrackRecordingService service, TrackRecordingServiceConnection self);
}
default void onDisconnected() { public static void execute(Context context, Callback callback) {
} Callback withUnbind = (service, connection) -> {
callback.onConnected(service, connection);
connection.unbind(context);
};
new TrackRecordingServiceConnection(withUnbind)
.bind(context);
ContextCompat.startForegroundService(context, new Intent(context, TrackRecordingService.class));
} }
} }
@@ -32,7 +32,7 @@ public class MarkerEditViewModel extends AndroidViewModel {
private MutableLiveData<Marker> markerData; private MutableLiveData<Marker> markerData;
private boolean isNewMarker; private boolean isNewMarker;
private Uri photoOriginalUri; private Uri photoOriginalUri;
private final TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection(); private final TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection((service, connection) -> {});
public MarkerEditViewModel(@NonNull Application application) { public MarkerEditViewModel(@NonNull Application application) {
super(application); super(application);
@@ -41,7 +41,7 @@ public class MarkerEditViewModel extends AndroidViewModel {
public LiveData<Marker> getMarkerData(@NonNull Track.Id trackId, @Nullable Marker.Id markerId) { public LiveData<Marker> getMarkerData(@NonNull Track.Id trackId, @Nullable Marker.Id markerId) {
if (markerData == null) { if (markerData == null) {
markerData = new MutableLiveData<>(); markerData = new MutableLiveData<>();
trackRecordingServiceConnection.startConnection(getApplication()); trackRecordingServiceConnection.bind(getApplication());
loadData(trackId, markerId); loadData(trackId, markerId);
} }
return markerData; return markerData;
@@ -119,7 +119,7 @@ public class MarkerListActivity extends AbstractActivity {
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
trackRecordingServiceConnection.startConnection(this); trackRecordingServiceConnection.bind(this);
} }
@Override @Override