diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java index 44148efa0..ed3914120 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java @@ -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(); diff --git a/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java b/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java index 46f628e28..08251014c 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java @@ -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)); } } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java index 41271a684..d8d9b4bef 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java @@ -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); diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackList.java b/MyTracks/src/com/google/android/apps/mytracks/TrackList.java index 30cfd7ac4..17e927f28 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/TrackList.java +++ b/MyTracks/src/com/google/android/apps/mytracks/TrackList.java @@ -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); + } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java b/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java index b31bfbefc..8c96698f1 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java +++ b/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java @@ -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); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/ServiceUtils.java b/MyTracks/src/com/google/android/apps/mytracks/services/ServiceUtils.java index 111b9f1bc..5268ed7f2 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/ServiceUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/ServiceUtils.java @@ -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 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() {} } diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java index 2961fb13d..e0d46a7bd 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java @@ -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() { diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingServiceBinder.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingServiceBinder.java deleted file mode 100644 index e216ee7ff..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingServiceBinder.java +++ /dev/null @@ -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 pendingBindCallbacks = - new WeakHashMap(); - - /** 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; - } -} diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingServiceConnection.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingServiceConnection.java new file mode 100644 index 000000000..b856e033a --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingServiceConnection.java @@ -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(); + } + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksTest.java index 9f6973d35..a5581edc7 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksTest.java @@ -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{ private SharedPreferences sharedPreferences; + private TrackRecordingServiceConnection serviceConnection; public MyTracksTest() { super(MyTracks.class); @@ -126,7 +128,7 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{ assertInitialized(); // Check if not recording. - clearSelectedAndRecordingTracks(); + clearSelectedAndRecordingTracks(); waitForIdle(); assertFalse(isRecording()); @@ -137,6 +139,7 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{ // 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{ 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{ // 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{ 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{ 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{ } private boolean isRecording() { - return ServiceUtils.isRecording(getActivity(), getSharedPreferences()); + return ServiceUtils.isRecording(getActivity(), + serviceConnection.getServiceIfBound(), getSharedPreferences()); } } diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java index 0f4c13ca8..64444be9f 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java @@ -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 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());