forked from upstream-mirrors/OpenTracks
Changing service connection to be a non-singleton, and properly handle its state.
This commit is contained in:
@@ -25,7 +25,7 @@ import com.google.android.apps.mytracks.content.WaypointCreationRequest;
|
||||
import com.google.android.apps.mytracks.io.file.TempFileCleaner;
|
||||
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
||||
import com.google.android.apps.mytracks.services.ServiceUtils;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceBinder;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
@@ -64,11 +64,16 @@ import android.widget.Toast;
|
||||
public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
private TrackDataHub dataHub;
|
||||
|
||||
/*
|
||||
/**
|
||||
* Menu manager.
|
||||
*/
|
||||
private MenuManager menuManager;
|
||||
|
||||
/**
|
||||
* Preferences.
|
||||
*/
|
||||
private SharedPreferences preferences;
|
||||
|
||||
/**
|
||||
* True if a new track should be created after the track recording service
|
||||
* binds.
|
||||
@@ -85,8 +90,6 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
*/
|
||||
private GoogleAnalyticsTracker tracker;
|
||||
|
||||
private TrackRecordingServiceBinder serviceBinder;
|
||||
|
||||
/*
|
||||
* Tabs/View navigation:
|
||||
*/
|
||||
@@ -99,23 +102,27 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Recording service interaction:
|
||||
*/
|
||||
|
||||
private final Runnable serviceBindCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (serviceBinder) {
|
||||
ITrackRecordingService service = serviceBinder.getServiceIfBound();
|
||||
synchronized (serviceConnection) {
|
||||
ITrackRecordingService service = serviceConnection.getServiceIfBound();
|
||||
if (startNewTrackRequested && service != null) {
|
||||
Log.i(TAG, "Starting recording");
|
||||
startNewTrackRequested = false;
|
||||
startRecordingNewTrack(service);
|
||||
} else {
|
||||
} else if (startNewTrackRequested) {
|
||||
Log.w(TAG, "Not yet starting recording");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private SharedPreferences preferences;
|
||||
private TrackRecordingServiceConnection serviceConnection;
|
||||
|
||||
/*
|
||||
* Application lifetime events:
|
||||
@@ -142,7 +149,7 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||
dataHub = TrackDataHub.newInstance(this);
|
||||
menuManager = new MenuManager(this);
|
||||
serviceBinder = TrackRecordingServiceBinder.getInstance(this);
|
||||
serviceConnection = new TrackRecordingServiceConnection(this, serviceBindCallback);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
int volumeStream =
|
||||
@@ -186,6 +193,45 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
Eula.showEulaRequireAcceptance(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
Log.d(TAG, "MyTracks.onStart");
|
||||
super.onStart();
|
||||
dataHub.start();
|
||||
|
||||
// Ensure that service is running and bound if we're supposed to be recording
|
||||
if (ServiceUtils.isRecording(this, null, preferences)) {
|
||||
serviceConnection.startAndBind();
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
Uri data = intent.getData();
|
||||
if ((Intent.ACTION_VIEW.equals(action) || Intent.ACTION_EDIT.equals(action))
|
||||
&& TracksColumns.CONTENT_ITEMTYPE.equals(intent.getType())
|
||||
&& UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
|
||||
long trackId = ContentUris.parseId(data);
|
||||
dataHub.loadTrack(trackId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
// Called when the current activity is being displayed or re-displayed
|
||||
// to the user.
|
||||
Log.d(TAG, "MyTracks.onResume");
|
||||
serviceConnection.bindIfRunning();
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
// Called when activity is going into the background, but has not (yet) been
|
||||
// killed. Shouldn't block longer than approx. 2 seconds.
|
||||
Log.d(TAG, "MyTracks.onPause");
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
Log.d(TAG, "MyTracks.onStop");
|
||||
@@ -201,50 +247,13 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
// Called when activity is going into the background, but has not (yet) been
|
||||
// killed. Shouldn't block longer than approx. 2 seconds.
|
||||
Log.d(TAG, "MyTracks.onPause");
|
||||
serviceBinder.unbindService();
|
||||
super.onPause();
|
||||
protected void onDestroy() {
|
||||
Log.d(TAG, "MyTracks.onDestroy");
|
||||
serviceConnection.unbind();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
// Called when the current activity is being displayed or re-displayed
|
||||
// to the user.
|
||||
Log.d(TAG, "MyTracks.onResume");
|
||||
serviceBinder.bindService(serviceBindCallback);
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
Log.d(TAG, "MyTracks.onStart");
|
||||
super.onStart();
|
||||
dataHub.start();
|
||||
|
||||
// Ensure that service is running if we're supposed to be recording
|
||||
if (ServiceUtils.isRecording(this, preferences)) {
|
||||
serviceBinder.startService();
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
Uri data = intent.getData();
|
||||
if ((Intent.ACTION_VIEW.equals(action) || Intent.ACTION_EDIT.equals(action))
|
||||
&& TracksColumns.CONTENT_ITEMTYPE.equals(intent.getType())
|
||||
&& UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
|
||||
long trackId = ContentUris.parseId(data);
|
||||
dataHub.loadTrack(trackId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Menu events:
|
||||
* ============
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
@@ -254,7 +263,7 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
menuManager.onPrepareOptionsMenu(menu, providerUtils.getLastTrack() != null,
|
||||
ServiceUtils.isRecording(this, preferences),
|
||||
ServiceUtils.isRecording(this, serviceConnection.getServiceIfBound(), preferences),
|
||||
dataHub.isATrackSelected());
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
@@ -273,8 +282,8 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
|
||||
@Override
|
||||
public boolean onTrackballEvent(MotionEvent event) {
|
||||
if (ServiceUtils.isRecording(this, preferences)) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (ServiceUtils.isRecording(this, serviceConnection.getServiceIfBound(), preferences)) {
|
||||
try {
|
||||
insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
} catch (RemoteException e) {
|
||||
@@ -285,6 +294,7 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onTrackballEvent(event);
|
||||
}
|
||||
|
||||
@@ -402,7 +412,7 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
* @throws RemoteException If the call on the service failed.
|
||||
*/
|
||||
private long insertWaypoint(WaypointCreationRequest request) throws RemoteException {
|
||||
ITrackRecordingService trackRecordingService = serviceBinder.getServiceIfBound();
|
||||
ITrackRecordingService trackRecordingService = serviceConnection.getServiceIfBound();
|
||||
if (trackRecordingService == null) {
|
||||
throw new IllegalStateException("The recording service is not bound.");
|
||||
}
|
||||
@@ -443,9 +453,9 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
* it. Starts recording a new track.
|
||||
*/
|
||||
void startRecording() {
|
||||
synchronized (serviceBinder) {
|
||||
synchronized (serviceConnection) {
|
||||
startNewTrackRequested = true;
|
||||
serviceBinder.startService();
|
||||
serviceConnection.startAndBind();
|
||||
|
||||
// Binding was already requested before, it either already happened
|
||||
// (in which case running the callback manually triggers the actual recording start)
|
||||
@@ -459,30 +469,29 @@ public class MyTracks extends TabActivity implements OnTouchListener {
|
||||
* Stops the track recording service and unbinds from it. Will display a toast
|
||||
* "Stopped recording" and pop up the Track Details activity.
|
||||
*/
|
||||
public void stopRecording() {
|
||||
ITrackRecordingService trackRecordingService = serviceBinder.getServiceIfBound();
|
||||
void stopRecording() {
|
||||
// Save the track id as the shared preference will overwrite the recording track id.
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||
long currentTrackId = sharedPreferences.getLong(getString(R.string.recording_track_key), -1);
|
||||
|
||||
ITrackRecordingService trackRecordingService = serviceConnection.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
// Save the track id as the shared preference will overwrite the recording track id.
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||
long currentTrackId = sharedPreferences.getLong(getString(R.string.recording_track_key), -1);
|
||||
try {
|
||||
trackRecordingService.endCurrentTrack();
|
||||
// TODO: We catch Exception, because after eliminating the service process
|
||||
// all exceptions it may throw are no longer wrapped in a RemoteException.
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to stop recording.", e);
|
||||
}
|
||||
|
||||
if (currentTrackId > 0) {
|
||||
Intent intent = new Intent(MyTracks.this, TrackDetails.class);
|
||||
intent.putExtra("trackid", currentTrackId);
|
||||
intent.putExtra("hasCancelButton", false);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
serviceBinder.stopService();
|
||||
}
|
||||
|
||||
serviceConnection.stop();
|
||||
|
||||
if (currentTrackId > 0) {
|
||||
Intent intent = new Intent(MyTracks.this, TrackDetails.class);
|
||||
intent.putExtra("trackid", currentTrackId);
|
||||
intent.putExtra("hasCancelButton", false);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
void clearSelectedTrack() {
|
||||
dataHub.unloadCurrentTrack();
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceBinder;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||
import com.google.android.apps.mytracks.services.sensors.SensorUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
@@ -46,7 +47,6 @@ public class SensorStateActivity extends Activity {
|
||||
private static final long REFRESH_PERIOD_MS = 250;
|
||||
|
||||
private final StatsUtilities utils;
|
||||
private TrackRecordingServiceBinder serviceBinder;
|
||||
|
||||
/**
|
||||
* This timer periodically invokes the refresh timer task.
|
||||
@@ -59,6 +59,11 @@ public class SensorStateActivity extends Activity {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Connection to the recording service.
|
||||
*/
|
||||
private TrackRecordingServiceConnection serviceConnection;
|
||||
|
||||
/**
|
||||
* A task which will update the U/I.
|
||||
*/
|
||||
@@ -81,39 +86,45 @@ public class SensorStateActivity extends Activity {
|
||||
|
||||
setContentView(R.layout.sensor_state);
|
||||
|
||||
serviceBinder = TrackRecordingServiceBinder.getInstance(this);
|
||||
serviceBinder.bindService(stateUpdater);
|
||||
// TODO: Allow reading from sensors even if service is not running.
|
||||
serviceConnection = new TrackRecordingServiceConnection(this, stateUpdater);
|
||||
serviceConnection.bindIfRunning();
|
||||
updateState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
serviceConnection.bindIfRunning();
|
||||
|
||||
timer = new Timer();
|
||||
timer.schedule(new RefreshTask(), REFRESH_PERIOD_MS, REFRESH_PERIOD_MS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
timer = null;
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
serviceBinder.unbindService();
|
||||
serviceConnection.unbind();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
protected void updateState() {
|
||||
ITrackRecordingService service = serviceBinder.getServiceIfBound();
|
||||
ITrackRecordingService service = serviceConnection.getServiceIfBound();
|
||||
if (service == null) {
|
||||
Log.d(Constants.TAG, "Could not get track recording service.");
|
||||
updateSensorState(Sensor.SensorState.NONE);
|
||||
updateSensorData(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Sensor.SensorDataSet sds = null;
|
||||
try {
|
||||
byte[] buff = service.getSensorData();
|
||||
@@ -159,8 +170,8 @@ public class SensorStateActivity extends Activity {
|
||||
|
||||
if (sds.hasPower() && sds.getPower().hasValue()
|
||||
&& sds.getPower().getState() == Sensor.SensorState.SENDING) {
|
||||
utils.setText(R.id.power_state_register,
|
||||
Integer.toString(sds.getPower().getValue()));
|
||||
utils.setText(R.id.power_state_register,
|
||||
Integer.toString(sds.getPower().getValue()));
|
||||
} else {
|
||||
utils.setText(R.id.power_state_register,
|
||||
SensorUtils.getStateAsString(
|
||||
@@ -186,14 +197,14 @@ public class SensorStateActivity extends Activity {
|
||||
if (sds.hasHeartRate() && sds.getHeartRate().hasValue()
|
||||
&& sds.getHeartRate().getState() == Sensor.SensorState.SENDING) {
|
||||
utils.setText(R.id.heart_rate_register,
|
||||
Integer.toString(sds.getHeartRate().getValue()));
|
||||
Integer.toString(sds.getHeartRate().getValue()));
|
||||
} else {
|
||||
utils.setText(R.id.heart_rate_register,
|
||||
SensorUtils.getStateAsString(
|
||||
sds.hasHeartRate()
|
||||
? sds.getHeartRate().getState()
|
||||
: Sensor.SensorState.NONE,
|
||||
this));
|
||||
utils.setText(R.id.heart_rate_register,
|
||||
SensorUtils.getStateAsString(
|
||||
sds.hasHeartRate()
|
||||
? sds.getHeartRate().getState()
|
||||
: Sensor.SensorState.NONE,
|
||||
this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(TAG, "Started UI update thread");
|
||||
while (ServiceUtils.isRecording(StatsActivity.this, preferences)) {
|
||||
while (ServiceUtils.isRecording(StatsActivity.this, null, preferences)) {
|
||||
runOnUiThread(updateResults);
|
||||
try {
|
||||
Thread.sleep(1000L);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
@@ -19,6 +19,7 @@ import com.google.android.apps.mytracks.content.TracksColumns;
|
||||
import com.google.android.apps.mytracks.io.file.SaveActivity;
|
||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendActivity;
|
||||
import com.google.android.apps.mytracks.services.ServiceUtils;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
@@ -79,8 +80,7 @@ public class TrackList extends ListActivity
|
||||
R.string.tracklist_show_track);
|
||||
menu.add(0, Constants.MENU_EDIT, 0,
|
||||
R.string.tracklist_edit_track);
|
||||
if (!ServiceUtils.isRecording(TrackList.this, null)
|
||||
|| trackId != recordingTrackId) {
|
||||
if (!isRecording() || trackId != recordingTrackId) {
|
||||
menu.add(0, Constants.MENU_SEND_TO_GOOGLE, 0,
|
||||
R.string.tracklist_send_to_google);
|
||||
SubMenu share = menu.addSubMenu(0, Constants.MENU_SHARE, 0,
|
||||
@@ -112,6 +112,16 @@ public class TrackList extends ListActivity
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable serviceBindingChanged = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateButtonsEnabled();
|
||||
}
|
||||
};
|
||||
|
||||
private TrackRecordingServiceConnection serviceConnection;
|
||||
private SharedPreferences preferences;
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(
|
||||
SharedPreferences sharedPreferences, String key) {
|
||||
@@ -214,16 +224,17 @@ public class TrackList extends ListActivity
|
||||
listView = getListView();
|
||||
listView.setOnCreateContextMenuListener(contextMenuListener);
|
||||
|
||||
SharedPreferences preferences =
|
||||
getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||
preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||
serviceConnection = new TrackRecordingServiceConnection(this, serviceBindingChanged);
|
||||
|
||||
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
|
||||
View exportAll = findViewById(R.id.tracklist_btn_export_all);
|
||||
boolean notRecording = !ServiceUtils.isRecording(this, preferences);
|
||||
deleteAll.setOnClickListener(this);
|
||||
deleteAll.setEnabled(notRecording);
|
||||
|
||||
View exportAll = findViewById(R.id.tracklist_btn_export_all);
|
||||
exportAll.setOnClickListener(this);
|
||||
exportAll.setEnabled(notRecording);
|
||||
|
||||
updateButtonsEnabled();
|
||||
|
||||
findViewById(R.id.tracklist_btn_import_all).setOnClickListener(this);
|
||||
|
||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||
@@ -238,6 +249,29 @@ public class TrackList extends ListActivity
|
||||
setListAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
serviceConnection.bindIfRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
serviceConnection.unbind();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void updateButtonsEnabled() {
|
||||
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
|
||||
View exportAll = findViewById(R.id.tracklist_btn_export_all);
|
||||
|
||||
boolean notRecording = !isRecording();
|
||||
deleteAll.setEnabled(notRecording);
|
||||
exportAll.setEnabled(notRecording);
|
||||
}
|
||||
|
||||
private void setListAdapter() {
|
||||
// Get a cursor with all tracks
|
||||
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
|
||||
@@ -301,4 +335,8 @@ public class TrackList extends ListActivity
|
||||
});
|
||||
setListAdapter(adapter);
|
||||
}
|
||||
|
||||
private boolean isRecording() {
|
||||
return ServiceUtils.isRecording(TrackList.this, serviceConnection.getServiceIfBound(), preferences);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
|
||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceBinder;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
@@ -63,6 +65,7 @@ public class WaypointsList extends ListActivity
|
||||
private Button insertStatisticsButton = null;
|
||||
private long recordingTrackId = -1;
|
||||
private MyTracksProviderUtils providerUtils;
|
||||
private TrackRecordingServiceConnection serviceConnection;
|
||||
|
||||
private Cursor waypointsCursor = null;
|
||||
|
||||
@@ -128,6 +131,7 @@ public class WaypointsList extends ListActivity
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||
serviceConnection = new TrackRecordingServiceConnection(this, null);
|
||||
|
||||
// We don't need a window title bar:
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
@@ -174,6 +178,20 @@ public class WaypointsList extends ListActivity
|
||||
setListAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
serviceConnection.bindIfRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
serviceConnection.unbind();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
WaypointCreationRequest request;
|
||||
@@ -201,8 +219,7 @@ public class WaypointsList extends ListActivity
|
||||
|
||||
private long insertWaypoint(WaypointCreationRequest request) {
|
||||
try {
|
||||
ITrackRecordingService trackRecordingService =
|
||||
TrackRecordingServiceBinder.getInstance(this).getServiceIfBound();
|
||||
ITrackRecordingService trackRecordingService = serviceConnection.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
long waypointId = trackRecordingService.insertWaypoint(request);
|
||||
if (waypointId >= 0) {
|
||||
@@ -210,6 +227,8 @@ public class WaypointsList extends ListActivity
|
||||
Toast.LENGTH_LONG).show();
|
||||
return waypointId;
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Not connected to service, not inserting waypoint");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "Cannot insert marker.", e);
|
||||
|
||||
@@ -20,11 +20,16 @@ import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.RunningServiceInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper for reading service state.
|
||||
*
|
||||
@@ -32,9 +37,17 @@ import android.util.Log;
|
||||
*/
|
||||
public class ServiceUtils {
|
||||
|
||||
public static boolean isRecording(Context ctx, SharedPreferences preferences) {
|
||||
TrackRecordingServiceBinder serviceBinder = TrackRecordingServiceBinder.getInstance(ctx);
|
||||
ITrackRecordingService service = serviceBinder.getServiceIfBound();
|
||||
/**
|
||||
* Checks whether we're currently recording.
|
||||
* The checking is done by calling the service, if provided, or alternatively by reading
|
||||
* recording state saved to preferences.
|
||||
*
|
||||
* @param ctx the current context
|
||||
* @param service the service, or null if not bound to it
|
||||
* @param preferences the preferences, or null if not available
|
||||
* @return true if the service is recording (or supposed to be recording), false otherwise
|
||||
*/
|
||||
public static boolean isRecording(Context ctx, ITrackRecordingService service, SharedPreferences preferences) {
|
||||
if (service != null) {
|
||||
try {
|
||||
return service.isRecording();
|
||||
@@ -51,5 +64,25 @@ public class ServiceUtils {
|
||||
return preferences.getLong(ctx.getString(R.string.recording_track_key), -1) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the recording service is currently running.
|
||||
*
|
||||
* @param ctx the current context
|
||||
* @return true if the service is running, false otherwise
|
||||
*/
|
||||
public static boolean isServiceRunning(Context ctx) {
|
||||
ActivityManager activityManager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
|
||||
|
||||
for (RunningServiceInfo serviceInfo : services) {
|
||||
ComponentName componentName = serviceInfo.service;
|
||||
String serviceName = componentName.getClassName();
|
||||
if (serviceName.equals(TrackRecordingService.class.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ServiceUtils() {}
|
||||
}
|
||||
|
||||
@@ -1116,17 +1116,50 @@ public class TrackRecordingService extends Service {
|
||||
*/
|
||||
private static class ServiceBinder extends ITrackRecordingService.Stub {
|
||||
private TrackRecordingService service;
|
||||
private DeathRecipient deathRecipient;
|
||||
|
||||
public ServiceBinder(TrackRecordingService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
// Logic for letting the actual service go up and down.
|
||||
|
||||
@Override
|
||||
public boolean isBinderAlive() {
|
||||
// Pretend dead if the service went down.
|
||||
return service != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pingBinder() {
|
||||
return isBinderAlive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void linkToDeath(DeathRecipient recipient, int flags) {
|
||||
deathRecipient = recipient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
|
||||
if (!isBinderAlive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
deathRecipient = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the reference to the outer class to minimize the leak.
|
||||
*/
|
||||
private void detachFromService() {
|
||||
this.service = null;
|
||||
attachInterface(null, null);
|
||||
|
||||
if (deathRecipient != null) {
|
||||
deathRecipient.binderDied();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkService() {
|
||||
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* A manager for the binding to the track recording service.
|
||||
* This uses reference counting so multiple callers can share a binding.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackRecordingServiceBinder {
|
||||
/** Singleton instance. */
|
||||
private static TrackRecordingServiceBinder instance;
|
||||
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
Log.d(Constants.TAG, "Service now connected.");
|
||||
|
||||
// Delay setting the service until we are done with initialization.
|
||||
ITrackRecordingService trackRecordingService =
|
||||
ITrackRecordingService.Stub.asInterface(service);
|
||||
synchronized (TrackRecordingServiceBinder.this) {
|
||||
TrackRecordingServiceBinder.this.trackRecordingService = trackRecordingService;
|
||||
try {
|
||||
for (Runnable callback : pendingBindCallbacks.keySet()) {
|
||||
if (callback != null) {
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
pendingBindCallbacks.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
Log.d(TAG, "Service now disconnected.");
|
||||
synchronized (TrackRecordingServiceBinder.this) {
|
||||
trackRecordingService = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Reference to the service, if bound. */
|
||||
private ITrackRecordingService trackRecordingService;
|
||||
|
||||
/** Count of bindings to the service. */
|
||||
private int bindCount = 0;
|
||||
|
||||
/** Set of callbacks to execute when the service connects. */
|
||||
private final WeakHashMap<Runnable, Object> pendingBindCallbacks =
|
||||
new WeakHashMap<Runnable, Object>();
|
||||
|
||||
/** Application context. */
|
||||
private final Context applicationContext;
|
||||
|
||||
/**
|
||||
* Starts the service with the default intent.
|
||||
*
|
||||
* This should only be called when the service will be put in recording mode,
|
||||
* as the service being started means it'll be listening to the GPS.
|
||||
* Notice that it is perfectly fine to call {@link #bindService} without calling
|
||||
* this at all.
|
||||
*/
|
||||
public void startService() {
|
||||
Intent intent = new Intent(applicationContext, TrackRecordingService.class);
|
||||
applicationContext.startService(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds to the service, and calls the given callback afterwards.
|
||||
* Calls to this method should be balanced with calls to {@link #unbindService}.
|
||||
*
|
||||
* @param onBindCallback the callback for when the service is connected
|
||||
*/
|
||||
public void bindService(Runnable onBindCallback) {
|
||||
synchronized (this) {
|
||||
bindCount++;
|
||||
if (trackRecordingService != null) {
|
||||
if (onBindCallback != null) {
|
||||
onBindCallback.run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (onBindCallback != null) {
|
||||
pendingBindCallbacks.put(onBindCallback, null);
|
||||
}
|
||||
|
||||
if (bindCount == 1) {
|
||||
Intent intent = new Intent(applicationContext, TrackRecordingService.class);
|
||||
int flags = SystemUtils.isRelease(applicationContext) ? 0 : Context.BIND_DEBUG_UNBIND;
|
||||
applicationContext.bindService(intent, serviceConnection, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbinds from the service.
|
||||
*
|
||||
* This will unbind from the service, unless another caller is still bound to it.
|
||||
* Calls to this method should be balanced with calls to {@link #bindService}.
|
||||
*/
|
||||
public void unbindService() {
|
||||
synchronized (this) {
|
||||
bindCount--;
|
||||
if (bindCount > 0) {
|
||||
// Someone else may still be using it.
|
||||
return;
|
||||
} else if (bindCount < 0) {
|
||||
Log.e(TAG, "Unbalanced binding calls.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
applicationContext.unbindService(serviceConnection);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(TAG, "Tried unbinding, but service was not registered.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the service, and consequently its GPS listening.
|
||||
*/
|
||||
public void stopService() {
|
||||
try {
|
||||
applicationContext.stopService(new Intent(applicationContext, TrackRecordingService.class));
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Encountered a security exception when trying to stop service.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
if (bindCount > 0) {
|
||||
Log.e(TAG, "Leaked bindings: " + bindCount + " bindings left on finalize.");
|
||||
}
|
||||
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connected service instance, or null if not connected.
|
||||
*/
|
||||
public ITrackRecordingService getServiceIfBound() {
|
||||
return trackRecordingService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton instance of this class.
|
||||
* Notice that although a context is required, it is safe to call this from
|
||||
* multiple different contexts to obtain the same instance.
|
||||
*
|
||||
* @param context the current context
|
||||
* @return the singleton instance
|
||||
*/
|
||||
public synchronized static TrackRecordingServiceBinder getInstance(Context context) {
|
||||
if (instance == null) {
|
||||
instance = new TrackRecordingServiceBinder(context.getApplicationContext());
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal constructor.
|
||||
*
|
||||
* @param applicationContext the current application (not activity) context
|
||||
*/
|
||||
private TrackRecordingServiceBinder(Context applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
import android.os.IBinder.DeathRecipient;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
public class TrackRecordingServiceConnection {
|
||||
private ITrackRecordingService boundService;
|
||||
|
||||
private final DeathRecipient deathRecipient = new DeathRecipient() {
|
||||
@Override
|
||||
public void binderDied() {
|
||||
Log.d(TAG, "Service died");
|
||||
setBoundService(null);
|
||||
}
|
||||
};
|
||||
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
Log.i(TAG, "Connected to service");
|
||||
try {
|
||||
service.linkToDeath(deathRecipient, 0);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to bind a death recipient", e);
|
||||
}
|
||||
|
||||
setBoundService(ITrackRecordingService.Stub.asInterface(service));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
Log.i(TAG, "Disconnected from service");
|
||||
setBoundService(null);
|
||||
}
|
||||
};
|
||||
|
||||
private final Context context;
|
||||
|
||||
private final Runnable bindChangedCallback;
|
||||
|
||||
public TrackRecordingServiceConnection(Context context, Runnable bindChangedCallback) {
|
||||
this.context = context;
|
||||
this.bindChangedCallback = bindChangedCallback;
|
||||
}
|
||||
|
||||
public void startAndBind() {
|
||||
bindService(true);
|
||||
}
|
||||
|
||||
public void bindIfRunning() {
|
||||
bindService(false);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
unbind();
|
||||
|
||||
Log.d(TAG, "Stopping service");
|
||||
Intent intent = new Intent(context, TrackRecordingService.class);
|
||||
context.stopService(intent);
|
||||
}
|
||||
|
||||
public void unbind() {
|
||||
Log.d(TAG, "Unbinding from the service");
|
||||
try {
|
||||
context.unbindService(serviceConnection);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Means we weren't bound, which is ok.
|
||||
}
|
||||
|
||||
setBoundService(null);
|
||||
}
|
||||
|
||||
public ITrackRecordingService getServiceIfBound() {
|
||||
return boundService;
|
||||
}
|
||||
|
||||
private void bindService(boolean startIfNeeded) {
|
||||
if (boundService != null) {
|
||||
// Already bound.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!startIfNeeded && !ServiceUtils.isServiceRunning(context)) {
|
||||
// Not running, start not requested.
|
||||
Log.d(TAG, "Service not running, not binding to it.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (startIfNeeded) {
|
||||
Log.i(TAG, "Starting the service");
|
||||
Intent intent = new Intent(context, TrackRecordingService.class);
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Binding to the service");
|
||||
Intent intent = new Intent(context, TrackRecordingService.class);
|
||||
int flags = SystemUtils.isRelease(context) ? 0 : Context.BIND_DEBUG_UNBIND;
|
||||
context.bindService(intent, serviceConnection, flags);
|
||||
}
|
||||
|
||||
private void setBoundService(ITrackRecordingService service) {
|
||||
boundService = service;
|
||||
if (bindChangedCallback != null) {
|
||||
bindChangedCallback.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mytracks.services.ServiceUtils;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -33,11 +34,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* A unit test for {@link MyTracks} activity.
|
||||
*
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
*/
|
||||
public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
||||
private SharedPreferences sharedPreferences;
|
||||
private TrackRecordingServiceConnection serviceConnection;
|
||||
|
||||
public MyTracksTest() {
|
||||
super(MyTracks.class);
|
||||
@@ -126,7 +128,7 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
||||
assertInitialized();
|
||||
|
||||
// Check if not recording.
|
||||
clearSelectedAndRecordingTracks();
|
||||
clearSelectedAndRecordingTracks();
|
||||
waitForIdle();
|
||||
|
||||
assertFalse(isRecording());
|
||||
@@ -137,6 +139,7 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
||||
|
||||
// Start a new track.
|
||||
getActivity().startRecording();
|
||||
serviceConnection.bindIfRunning();
|
||||
long recordingTrackId = awaitRecordingStatus(5000, true);
|
||||
assertTrue(recordingTrackId >= 0);
|
||||
|
||||
@@ -149,7 +152,7 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
||||
assertEquals(recordingTrackId, selectedTrackId);
|
||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
||||
|
||||
// Watch for MyTracksDetails activity.
|
||||
// Watch for MyTracksDetails activity.
|
||||
ActivityMonitor monitor = getInstrumentation().addMonitor(
|
||||
TrackDetails.class.getName(), null, false);
|
||||
|
||||
@@ -157,7 +160,7 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
||||
// no longer recording.
|
||||
getActivity().stopRecording();
|
||||
|
||||
// Check if we got back MyTracksDetails activity.
|
||||
// Check if we got back MyTracksDetails activity.
|
||||
Activity activity = getInstrumentation().waitForMonitor(monitor);
|
||||
assertTrue(activity instanceof TrackDetails);
|
||||
|
||||
@@ -180,13 +183,15 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
||||
|
||||
private void assertInitialized() {
|
||||
assertNotNull(getActivity());
|
||||
|
||||
serviceConnection = new TrackRecordingServiceConnection(getActivity(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until the UI thread becomes idle.
|
||||
*/
|
||||
private void waitForIdle() throws InterruptedException {
|
||||
// Note: We can't use getInstrumentation().waitForIdleSync() here.
|
||||
// Note: We can't use getInstrumentation().waitForIdleSync() here.
|
||||
final Object semaphore = new Object();
|
||||
synchronized (semaphore) {
|
||||
final AtomicBoolean isIdle = new AtomicBoolean();
|
||||
@@ -216,10 +221,10 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits until the recording state changes to the given status.
|
||||
*
|
||||
*
|
||||
* @param timeout the maximum time to wait, in milliseconds.
|
||||
* @param isRecording the final status to await.
|
||||
* @return the recording track ID.
|
||||
@@ -250,6 +255,7 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
||||
}
|
||||
|
||||
private boolean isRecording() {
|
||||
return ServiceUtils.isRecording(getActivity(), getSharedPreferences());
|
||||
return ServiceUtils.isRecording(getActivity(),
|
||||
serviceConnection.getServiceIfBound(), getSharedPreferences());
|
||||
}
|
||||
}
|
||||
|
||||
+39
-112
@@ -49,9 +49,9 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Tests for the MyTracks track recording service.
|
||||
*
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
*
|
||||
*
|
||||
* TODO: The original class, ServiceTestCase, has a few limitations, e.g.
|
||||
* it's not possible to properly shutdown the service, unless tearDown()
|
||||
* is called, which prevents from testing multiple scenarios in a single
|
||||
@@ -63,7 +63,7 @@ public class TrackRecordingServiceTest
|
||||
private Context context;
|
||||
private MyTracksProviderUtils providerUtils;
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
|
||||
/*
|
||||
* In order to support starting and binding to the service in the same
|
||||
* unit test, we provide a workaround, as the original class doesn't allow
|
||||
@@ -72,19 +72,19 @@ public class TrackRecordingServiceTest
|
||||
|
||||
private boolean bound;
|
||||
private Intent serviceIntent;
|
||||
|
||||
|
||||
public TrackRecordingServiceTest() {
|
||||
super(TrackRecordingService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* A context wrapper with the user provided {@link ContentResolver}.
|
||||
*
|
||||
*
|
||||
* TODO: Move to test utils package.
|
||||
*/
|
||||
public static class MockContext extends ContextWrapper {
|
||||
private final ContentResolver contentResolver;
|
||||
|
||||
|
||||
public MockContext(ContentResolver contentResolver, Context base) {
|
||||
super(base);
|
||||
this.contentResolver = contentResolver;
|
||||
@@ -108,7 +108,7 @@ public class TrackRecordingServiceTest
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected IBinder bindService(Intent intent) {
|
||||
if (getService() != null) {
|
||||
@@ -190,7 +190,7 @@ public class TrackRecordingServiceTest
|
||||
startService(startIntent);
|
||||
assertNotNull(getService());
|
||||
|
||||
// We expect to resume the previous track.
|
||||
// We expect to resume the previous track.
|
||||
assertTrue(getService().isRecording());
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
assertEquals(123, service.getRecordingTrackId());
|
||||
@@ -293,7 +293,7 @@ public class TrackRecordingServiceTest
|
||||
assertTrue(tracks.isEmpty());
|
||||
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
// Test if we start in no-recording mode by default.
|
||||
// Test if we start in no-recording mode by default.
|
||||
assertFalse(service.isRecording());
|
||||
assertEquals(-1, service.getRecordingTrackId());
|
||||
}
|
||||
@@ -463,92 +463,19 @@ public class TrackRecordingServiceTest
|
||||
context.getString(R.string.recording_track_key), 0));
|
||||
assertEquals(-1, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
|
||||
@MediumTest
|
||||
public void testIntegration_completeRecordingSession() throws Exception {
|
||||
List<Track> tracks = providerUtils.getAllTracks();
|
||||
assertTrue(tracks.isEmpty());
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testDeleteAllTracks_noRecording() throws Exception {
|
||||
createDummyTrack(123, -1, false);
|
||||
assertEquals(1, providerUtils.getAllTracks().size());
|
||||
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
// Deleting all tracks should succeed.
|
||||
service.deleteAllTracks();
|
||||
assertFalse(service.isRecording());
|
||||
assertTrue(providerUtils.getAllTracks().isEmpty());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testDeleteAllTracks_noTracks() throws Exception {
|
||||
assertTrue(providerUtils.getAllTracks().isEmpty());
|
||||
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
// Deleting all tracks should succeed.
|
||||
service.deleteAllTracks();
|
||||
assertFalse(service.isRecording());
|
||||
assertTrue(providerUtils.getAllTracks().isEmpty());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testDeleteAllTracks_trackInProgress() throws Exception {
|
||||
createDummyTrack(123, -1, true);
|
||||
assertEquals(1, providerUtils.getAllTracks().size());
|
||||
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
assertTrue(service.isRecording());
|
||||
|
||||
// Since we have a track in progress, we expect to fail.
|
||||
try {
|
||||
service.deleteAllTracks();
|
||||
fail("Expecting IllegalStateException");
|
||||
} catch (IllegalStateException e) {
|
||||
// Expected.
|
||||
}
|
||||
assertTrue(service.isRecording());
|
||||
assertEquals(1, providerUtils.getAllTracks().size());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testHasRecorded_noTracks() throws Exception {
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
assertFalse(service.isRecording());
|
||||
assertFalse(service.hasRecorded());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testHasRecorded_trackInProgress() throws Exception {
|
||||
createDummyTrack(123, -1, true);
|
||||
assertEquals(1, providerUtils.getAllTracks().size());
|
||||
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
assertTrue(service.isRecording());
|
||||
assertTrue(service.hasRecorded());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testHasRecorded_oldTracks() throws Exception {
|
||||
createDummyTrack(123, -1, false);
|
||||
assertEquals(1, providerUtils.getAllTracks().size());
|
||||
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
assertFalse(service.isRecording());
|
||||
assertTrue(service.hasRecorded());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
|
||||
try {
|
||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
fail("Expecting IllegalStateException");
|
||||
@@ -556,17 +483,17 @@ public class TrackRecordingServiceTest
|
||||
// Expected.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@MediumTest
|
||||
public void testInsertStatisticsMarker_validLocation() throws Exception {
|
||||
createDummyTrack(123, -1, true);
|
||||
|
||||
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
assertTrue(service.isRecording());
|
||||
|
||||
|
||||
assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
|
||||
assertEquals(2, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
|
||||
|
||||
|
||||
Waypoint wpt = providerUtils.getWaypoint(1);
|
||||
assertEquals(getContext().getString(R.string.stats_icon_url),
|
||||
wpt.getIcon());
|
||||
@@ -586,7 +513,7 @@ public class TrackRecordingServiceTest
|
||||
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
|
||||
try {
|
||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_MARKER);
|
||||
fail("Expecting IllegalStateException");
|
||||
@@ -594,14 +521,14 @@ public class TrackRecordingServiceTest
|
||||
// Expected.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@MediumTest
|
||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
||||
createDummyTrack(123, -1, true);
|
||||
|
||||
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
assertTrue(service.isRecording());
|
||||
|
||||
|
||||
assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_MARKER));
|
||||
Waypoint wpt = providerUtils.getWaypoint(1);
|
||||
assertEquals(getContext().getString(R.string.waypoint_icon_url),
|
||||
@@ -614,7 +541,7 @@ public class TrackRecordingServiceTest
|
||||
assertNotNull(wpt.getLocation());
|
||||
assertNull(wpt.getStatistics());
|
||||
}
|
||||
|
||||
|
||||
@MediumTest
|
||||
public void testWithProperties_noAnnouncementFreq() throws Exception {
|
||||
functionalTest(R.string.announcement_frequency_key, (Object) null);
|
||||
@@ -624,7 +551,7 @@ public class TrackRecordingServiceTest
|
||||
public void testWithProperties_defaultAnnouncementFreq() throws Exception {
|
||||
functionalTest(R.string.announcement_frequency_key, 1);
|
||||
}
|
||||
|
||||
|
||||
@MediumTest
|
||||
public void testWithProperties_noMaxRecordingDist() throws Exception {
|
||||
functionalTest(R.string.max_recording_distance_key, (Object) null);
|
||||
@@ -654,7 +581,7 @@ public class TrackRecordingServiceTest
|
||||
public void testWithProperties_defaultSplitFreqByDist() throws Exception {
|
||||
functionalTest(R.string.split_frequency_key, 5);
|
||||
}
|
||||
|
||||
|
||||
@MediumTest
|
||||
public void testWithProperties_defaultSplitFreqByTime() throws Exception {
|
||||
functionalTest(R.string.split_frequency_key, -2);
|
||||
@@ -674,7 +601,7 @@ public class TrackRecordingServiceTest
|
||||
public void testWithProperties_metricUnitsDisabled() throws Exception {
|
||||
functionalTest(R.string.metric_units_key, false);
|
||||
}
|
||||
|
||||
|
||||
@MediumTest
|
||||
public void testWithProperties_noMinRecordingInterval() throws Exception {
|
||||
functionalTest(R.string.min_recording_interval_key, (Object) null);
|
||||
@@ -690,23 +617,23 @@ public class TrackRecordingServiceTest
|
||||
public void testWithProperties_noMinRequiredAccuracy() throws Exception {
|
||||
functionalTest(R.string.min_required_accuracy_key, (Object) null);
|
||||
}
|
||||
|
||||
|
||||
@MediumTest
|
||||
public void testWithProperties_defaultMinRequiredAccuracy() throws Exception {
|
||||
functionalTest(R.string.min_required_accuracy_key, 500);
|
||||
}
|
||||
|
||||
|
||||
@MediumTest
|
||||
public void testWithProperties_noSensorType() throws Exception {
|
||||
functionalTest(R.string.sensor_type_key, (Object) null);
|
||||
}
|
||||
|
||||
|
||||
@MediumTest
|
||||
public void testWithProperties_zephyrSensorType() throws Exception {
|
||||
functionalTest(R.string.sensor_type_key,
|
||||
context.getString(R.string.zephyr_sensor_type));
|
||||
context.getString(R.string.zephyr_sensor_type));
|
||||
}
|
||||
|
||||
|
||||
private ITrackRecordingService bindAndGetService(Intent intent) {
|
||||
ITrackRecordingService service = ITrackRecordingService.Stub.asInterface(
|
||||
bindService(intent));
|
||||
@@ -724,7 +651,7 @@ public class TrackRecordingServiceTest
|
||||
addTrack(dummyTrack, isRecording);
|
||||
return dummyTrack;
|
||||
}
|
||||
|
||||
|
||||
private void updateAutoResumePrefs(int attempts, int timeoutMins) {
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putInt(context.getString(
|
||||
@@ -733,20 +660,20 @@ public class TrackRecordingServiceTest
|
||||
R.string.auto_resume_track_timeout_key), timeoutMins);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
|
||||
private Intent createStartIntent() {
|
||||
Intent startIntent = new Intent();
|
||||
startIntent.setClass(context, TrackRecordingService.class);
|
||||
return startIntent;
|
||||
}
|
||||
|
||||
|
||||
private void addTrack(Track track, boolean isRecording) {
|
||||
assertTrue(track.getId() >= 0);
|
||||
providerUtils.insertTrack(track);
|
||||
assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId());
|
||||
setRecordingTrack(isRecording ? track.getId() : -1);
|
||||
}
|
||||
|
||||
|
||||
private void setRecordingTrack(long id) {
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putLong(context.getString(R.string.recording_track_key), id);
|
||||
@@ -760,11 +687,11 @@ public class TrackRecordingServiceTest
|
||||
throws Exception {
|
||||
final String key = context.getString(resourceId);
|
||||
for (Object value : values) {
|
||||
// Remove all properties and set the property for the given key.
|
||||
// Remove all properties and set the property for the given key.
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.clear();
|
||||
if (value instanceof String) {
|
||||
editor.putString(key, (String) value);
|
||||
editor.putString(key, (String) value);
|
||||
} else if (value instanceof Long) {
|
||||
editor.putLong(key, (Long) value);
|
||||
} else if (value instanceof Integer) {
|
||||
@@ -775,15 +702,15 @@ public class TrackRecordingServiceTest
|
||||
// Do nothing, as clear above has already removed this property.
|
||||
}
|
||||
editor.commit();
|
||||
|
||||
|
||||
fullRecordingSession();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void fullRecordingSession() throws Exception {
|
||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
|
||||
// Start a track.
|
||||
long id = service.startNewTrack();
|
||||
assertTrue(id >= 0);
|
||||
@@ -806,14 +733,14 @@ public class TrackRecordingServiceTest
|
||||
loc.setTime(startTime + i * 10000);
|
||||
loc.setBearing(3.0f);
|
||||
service.recordLocation(loc);
|
||||
|
||||
|
||||
if (i % 10 == 0) {
|
||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
} else if (i % 7 == 0) {
|
||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_MARKER);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Stop the track. Validate if it has correct data.
|
||||
service.endCurrentTrack();
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
Reference in New Issue
Block a user