Extracted TrackRecordingService's Binder.

This commit is contained in:
Dennis Guse
2020-01-04 23:21:59 +01:00
parent 543a04dfdd
commit 070917bc0b
9 changed files with 151 additions and 143 deletions
@@ -95,7 +95,7 @@ public class TrackRecordingServiceTest {
@After
public void tearDown() throws TimeoutException {
// Reset service (if some previous test failed)
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
@@ -126,7 +126,7 @@ public class TrackRecordingServiceTest {
Intent startIntent = createStartIntent(context);
mServiceRule.startService(startIntent);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent));
// Test if we start in no-recording mode by default.
Assert.assertFalse(service.isRecording());
@@ -138,7 +138,7 @@ public class TrackRecordingServiceTest {
public void testRecording_oldTracks() throws Exception {
createDummyTrack(trackId, -1L, false);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
}
@@ -147,7 +147,7 @@ public class TrackRecordingServiceTest {
@Test
public void testRecording_orphanedRecordingTrack() throws Exception {
Intent startIntent = createStartIntent(context);
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent));
// Just set recording track to a bogus value.
// Make sure that the service will not start recording and will clear the bogus track.
@@ -161,7 +161,7 @@ public class TrackRecordingServiceTest {
@MediumTest
@Test
public void testStartNewTrack_alreadyRecording() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack();
Assert.assertTrue(service.isRecording());
long trackId = service.getRecordingTrackId();
@@ -178,7 +178,7 @@ public class TrackRecordingServiceTest {
@MediumTest
@Test
public void testEndCurrentTrack_noRecording() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
// Ending the current track when there is no recording should not result in any error.
@@ -210,7 +210,7 @@ public class TrackRecordingServiceTest {
/**
* Inserts a location and waits for 200ms.
*/
private void insertLocation(ITrackRecordingService trackRecordingService) throws InterruptedException {
private void insertLocation(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException {
Location location = new Location("gps");
location.setLongitude(35.0f);
location.setLatitude(45.0f);
@@ -226,7 +226,7 @@ public class TrackRecordingServiceTest {
@MediumTest
@Test
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
long waypointId = service.insertWaypoint(null, null, null, null);
@@ -236,7 +236,7 @@ public class TrackRecordingServiceTest {
@MediumTest
@Test
public void testInsertWaypointMarker_validWaypoint() throws Exception {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack();
Assert.assertTrue(service.isRecording());
insertLocation(service);
@@ -80,7 +80,7 @@ public class TrackRecordingServiceTestLooper {
@After
public void tearDown() throws TimeoutException {
// Reset service (if some previous test failed)
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
if (service.isRecording() || service.isPaused()) {
service.endCurrentTrack();
}
@@ -192,7 +192,7 @@ public class TrackRecordingServiceTestLooper {
}
private void fullRecordingSession() throws TimeoutException {
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
Assert.assertFalse(service.isRecording());
// Start a track.
@@ -23,8 +23,8 @@ import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.TextView;
import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
@@ -142,7 +142,7 @@ public class TrackController {
* Gets the total time for the current recording track.
*/
private long getTotalTime() {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
return trackRecordingService != null ? trackRecordingService.getTotalTime() : 0L;
}
}
@@ -53,8 +53,8 @@ import java.util.Locale;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.IntentUtils;
@@ -192,7 +192,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
return;
}
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) {
Log.d(TAG, "service not available to start gps or a new recording");
return;
@@ -405,7 +405,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
trackRecordingServiceConnection.startAndBind(this);
bindChangedCallback.run();
} else {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
if (trackRecordingService != null) {
trackRecordingService.stopGps();
}
@@ -44,8 +44,8 @@ import de.dennisguse.opentracks.content.TrackDataType;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.ITrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.stats.TripStatistics;
import de.dennisguse.opentracks.util.LocationUtils;
@@ -441,7 +441,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
* Tries to fetch most recent {@link SensorDataSet} {@link de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager}.
*/
private void updateSensorDataUI() {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
SensorDataSet sensorDataSet = null;
if (trackRecordingService == null) {
@@ -34,7 +34,6 @@ import android.os.IBinder;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.TaskStackBuilder;
import java.util.concurrent.ExecutorService;
@@ -154,7 +153,7 @@ public class TrackRecordingService extends Service {
private boolean currentSegmentHasLocation;
private boolean isIdle;
private ServiceBinder binder = new ServiceBinder(this);
private TrackRecordingServiceBinder binder = new TrackRecordingServiceBinder(this);
private final LocationListener locationListener = new LocationListener() {
@Override
@@ -286,6 +285,10 @@ public class TrackRecordingService extends Service {
return recordingTrackPaused;
}
public long getRecordingTrackId() {
return recordingTrackId;
}
public TripStatistics getTripStatistics() {
if (trackTripStatisticsUpdater == null) {
return null;
@@ -293,6 +296,16 @@ public class TrackRecordingService extends Service {
return trackTripStatisticsUpdater.getTripStatistics();
}
public long getTotalTime() {
if (trackTripStatisticsUpdater == null) {
return 0;
}
if (!isPaused()) {
trackTripStatisticsUpdater.updateTime(System.currentTimeMillis());
}
return trackTripStatisticsUpdater.getTripStatistics().getTotalTime();
}
/**
* Inserts a waypoint.
*
@@ -337,7 +350,7 @@ public class TrackRecordingService extends Service {
*
* @return the track id
*/
private long startNewTrack() {
long startNewTrack() {
if (isRecording()) {
Log.d(TAG, "Ignore startNewTrack. Already recording.");
return -1L;
@@ -384,7 +397,7 @@ public class TrackRecordingService extends Service {
startRecording();
}
private void resumeCurrentTrack() {
void resumeCurrentTrack() {
if (!isRecording() || !isPaused()) {
Log.d(TAG, "Ignore resumeCurrentTrack. Not recording or not paused.");
return;
@@ -424,13 +437,13 @@ public class TrackRecordingService extends Service {
voiceExecutor.restore();
}
private void startGps() {
void startGps() {
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
registerLocationListener();
showNotification(true);
}
private void endCurrentTrack() {
void endCurrentTrack() {
if (!isRecording()) {
Log.d(TAG, "Ignore endCurrentTrack. Not recording.");
return;
@@ -463,7 +476,7 @@ public class TrackRecordingService extends Service {
endRecording(true);
}
private void pauseCurrentTrack() {
void pauseCurrentTrack() {
if (!isRecording() || isPaused()) {
Log.d(TAG, "Ignore pauseCurrentTrack. Not recording or paused.");
return;
@@ -514,7 +527,7 @@ public class TrackRecordingService extends Service {
*
* @param stop true to stop self
*/
private void stopGps(boolean stop) {
void stopGps(boolean stop) {
unregisterLocationListener();
showNotification(false);
releaseWakeLock();
@@ -549,12 +562,7 @@ public class TrackRecordingService extends Service {
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, recordingTrackPaused);
}
/**
* Called when location changed.
*
* @param location the location
*/
private void onLocationChangedAsync(Location location) {
void onLocationChangedAsync(Location location) {
if (!isRecording() || isPaused()) {
Log.w(TAG, "Ignore onLocationChangedAsync. Not recording or paused.");
return;
@@ -693,7 +701,7 @@ public class TrackRecordingService extends Service {
contentProviderUtils.updateTrack(track);
}
private SensorDataSet getSensorDataSet() {
SensorDataSet getSensorDataSet() {
if (remoteSensorManager == null || !remoteSensorManager.isEnabled() || !remoteSensorManager.isSensorDataSetValid()) {
return null;
}
@@ -761,105 +769,4 @@ public class TrackRecordingService extends Service {
startForeground(NOTIFICATION_ID, notificationManager.getNotification());
}
}
/**
* TODO: There is a bug in Android that leaks Binder instances. This bug is
* especially visible if we have a non-static class, as there is no way to
* nullify reference to the outer class (the service). A workaround is to use
* a static class and explicitly clear service and detach it from the
* underlying Binder. With this approach, we minimize the leak to 24 bytes per
* each service instance. For more details, see the following bug:
* http://code.google.com/p/android/issues/detail?id=6426.
*/
private static class ServiceBinder extends android.os.Binder implements ITrackRecordingService {
private TrackRecordingService trackRecordingService;
public ServiceBinder(TrackRecordingService trackRecordingService) {
this.trackRecordingService = trackRecordingService;
}
@Override
public void startGps() {
if (!trackRecordingService.isRecording()) {
trackRecordingService.startGps();
}
}
public void stopGps() {
if (!trackRecordingService.isRecording()) {
trackRecordingService.stopGps(true);
}
}
@Override
public long startNewTrack() {
return trackRecordingService.startNewTrack();
}
@Override
public void pauseCurrentTrack() {
trackRecordingService.pauseCurrentTrack();
}
@Override
public void resumeCurrentTrack() {
trackRecordingService.resumeCurrentTrack();
}
@Override
public void endCurrentTrack() {
trackRecordingService.endCurrentTrack();
}
@Override
public boolean isRecording() {
return trackRecordingService.isRecording();
}
@Override
public boolean isPaused() {
return trackRecordingService.isPaused();
}
@Override
public long getRecordingTrackId() {
return trackRecordingService.recordingTrackId;
}
@Override
public long getTotalTime() {
TripStatisticsUpdater updater = trackRecordingService.trackTripStatisticsUpdater;
if (updater == null) {
return 0;
}
if (!trackRecordingService.isPaused()) {
updater.updateTime(System.currentTimeMillis());
}
return updater.getTripStatistics().getTotalTime();
}
@Override
public long insertWaypoint(String name, String category, String description, String photoUrl) {
return trackRecordingService.insertWaypoint(name, category, description, photoUrl);
}
@VisibleForTesting
@Override
public void insertTrackPoint(Location location) {
trackRecordingService.onLocationChangedAsync(location);
}
@Override
public SensorDataSet getSensorData() {
return trackRecordingService.getSensorDataSet();
}
/**
* Detaches from the track recording service. Clears the reference to the
* outer class to minimize the leak.
*/
private void detachFromService() {
trackRecordingService = null;
}
}
}
@@ -0,0 +1,101 @@
package de.dennisguse.opentracks.services;
import android.location.Location;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
/**
* TODO: There is a bug in Android that leaks Binder instances. This bug is
* especially visible if we have a non-static class, as there is no way to
* nullify reference to the outer class (the service). A workaround is to use
* a static class and explicitly clear service and detach it from the
* underlying Binder. With this approach, we minimize the leak to 24 bytes per
* each service instance. For more details, see the following bug:
* http://code.google.com/p/android/issues/detail?id=6426.
*/
class TrackRecordingServiceBinder extends android.os.Binder implements TrackRecordingServiceInterface {
private TrackRecordingService trackRecordingService;
TrackRecordingServiceBinder(TrackRecordingService trackRecordingService) {
this.trackRecordingService = trackRecordingService;
}
@Override
public void startGps() {
if (!trackRecordingService.isRecording()) {
trackRecordingService.startGps();
}
}
public void stopGps() {
if (!trackRecordingService.isRecording()) {
trackRecordingService.stopGps(true);
}
}
@Override
public long startNewTrack() {
return trackRecordingService.startNewTrack();
}
@Override
public void pauseCurrentTrack() {
trackRecordingService.pauseCurrentTrack();
}
@Override
public void resumeCurrentTrack() {
trackRecordingService.resumeCurrentTrack();
}
@Override
public void endCurrentTrack() {
trackRecordingService.endCurrentTrack();
}
@Override
public boolean isRecording() {
return trackRecordingService.isRecording();
}
@Override
public boolean isPaused() {
return trackRecordingService.isPaused();
}
@Override
public long getRecordingTrackId() {
return trackRecordingService.getRecordingTrackId();
}
@Override
public long getTotalTime() {
return trackRecordingService.getTotalTime();
}
@Override
public long insertWaypoint(String name, String category, String description, String photoUrl) {
return trackRecordingService.insertWaypoint(name, category, description, photoUrl);
}
@VisibleForTesting
@Override
public void insertTrackPoint(Location location) {
trackRecordingService.onLocationChangedAsync(location);
}
@Override
public SensorDataSet getSensorData() {
return trackRecordingService.getSensorDataSet();
}
/**
* Detaches from the track recording service. Clears the reference to the
* outer class to minimize the leak.
*/
void detachFromService() {
trackRecordingService = null;
}
}
@@ -49,7 +49,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
private final Runnable callback;
private ITrackRecordingService trackRecordingService;
private TrackRecordingServiceInterface trackRecordingService;
/**
* Constructor.
@@ -86,7 +86,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
/**
* Gets the track recording service if bound. Returns null otherwise
*/
public ITrackRecordingService getServiceIfBound() {
public TrackRecordingServiceInterface getServiceIfBound() {
return trackRecordingService;
}
@@ -96,7 +96,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
*
* @param value the value
*/
private void setTrackRecordingService(ITrackRecordingService value) {
private void setTrackRecordingService(TrackRecordingServiceInterface value) {
trackRecordingService = value;
if (callback != null) {
callback.run();
@@ -123,7 +123,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
} catch (RemoteException e) {
Log.e(TAG, "Failed to bind a death recipient.", e);
}
setTrackRecordingService((ITrackRecordingService) service);
setTrackRecordingService((TrackRecordingServiceInterface) service);
}
@Override
@@ -180,7 +180,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* Resumes the recording track.
*/
public void resumeTrack() {
ITrackRecordingService service = getServiceIfBound();
TrackRecordingServiceInterface service = getServiceIfBound();
if (service != null) {
service.resumeCurrentTrack();
}
@@ -190,7 +190,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* Pauses the recording track.
*/
public void pauseTrack() {
ITrackRecordingService service = getServiceIfBound();
TrackRecordingServiceInterface service = getServiceIfBound();
if (service != null) {
service.pauseCurrentTrack();
}
@@ -212,7 +212,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* @return the id of the marker or -1L if none could be created.
*/
public long addMarker(Context context, String name, String category, String description, String photoUrl) {
ITrackRecordingService trackRecordingService = getServiceIfBound();
TrackRecordingServiceInterface trackRecordingService = getServiceIfBound();
if (trackRecordingService == null) {
Log.d(TAG, "Unable to add marker, no track recording service");
} else {
@@ -238,7 +238,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
* @param showEditor true to show the editor
*/
public void stopRecording(@NonNull Context context, boolean showEditor) {
ITrackRecordingService trackRecordingService = getServiceIfBound();
TrackRecordingServiceInterface trackRecordingService = getServiceIfBound();
if (trackRecordingService == null) {
resetRecordingState(context);
} else {
@@ -25,7 +25,7 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
* App's service.
* This service is the process that actually records and manages tracks.
*/
public interface ITrackRecordingService {
public interface TrackRecordingServiceInterface {
/**
* Starts gps.