diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java index 613e73a3a..978070379 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java @@ -23,26 +23,23 @@ import com.google.android.apps.mytracks.content.TracksColumns; 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.TrackRecordingService; +import com.google.android.apps.mytracks.services.TrackRecordingServiceBinder; import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory; import com.google.android.apps.mytracks.util.ApiFeatures; -import com.google.android.apps.mytracks.util.UriUtils; import com.google.android.apps.mytracks.util.SystemUtils; +import com.google.android.apps.mytracks.util.UriUtils; import com.google.android.maps.mytracks.R; import android.app.Activity; import android.app.AlertDialog; import android.app.TabActivity; -import android.content.ComponentName; import android.content.ContentUris; import android.content.DialogInterface; import android.content.Intent; -import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; -import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.Menu; @@ -60,6 +57,7 @@ import android.widget.Toast; * The super activity that embeds our sub activities. * * @author Leif Hendrik Wilden + * @author Rodrigo Damazio */ public class MyTracks extends TabActivity implements OnTouchListener { private TrackDataHub dataHub; @@ -80,48 +78,12 @@ public class MyTracks extends TabActivity implements OnTouchListener { */ private boolean startNewTrackRequested = false; - private ITrackRecordingService trackRecordingService; - /** * Utilities to deal with the database. */ private MyTracksProviderUtils providerUtils; - private SharedPreferences sharedPreferences; - - /** - * The connection to the track recording service. - */ - private final ServiceConnection serviceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - Log.d(Constants.TAG, "MyTracks: Service now connected."); - // Delay setting the service until we are done with initialization. - ITrackRecordingService trackRecordingService = - ITrackRecordingService.Stub.asInterface(service); - try { - // TODO: Send a start service intent and broadcast service started - // message to avoid the hack below and a race condition. - if (startNewTrackRequested) { - startNewTrackRequested = false; - startRecordingNewTrack(trackRecordingService); - } - } finally { - MyTracks.this.trackRecordingService = trackRecordingService; - } - } - - @Override - public void onServiceDisconnected(ComponentName className) { - Log.d(TAG, "MyTracks: Service now disconnected."); - trackRecordingService = null; - } - }; - - /** - * Whether {@link #serviceConnection} is bound or not. - */ - private boolean isBound = false; + private TrackRecordingServiceBinder serviceBinder; /* * Tabs/View navigation: @@ -135,6 +97,18 @@ public class MyTracks extends TabActivity implements OnTouchListener { } }; + private final Runnable serviceBindCallback = new Runnable() { + @Override + public void run() { + synchronized (serviceBinder) { + ITrackRecordingService service = serviceBinder.getServiceIfBound(); + if (startNewTrackRequested && service != null) { + startRecordingNewTrack(service); + } + } + } + }; + /** * Checks whether we have a track recording session in progress. * In some cases, when the service has crashed or has been restarted @@ -145,6 +119,7 @@ public class MyTracks extends TabActivity implements OnTouchListener { * based on settings from the shared preferences. */ public boolean isRecording() { + ITrackRecordingService trackRecordingService = serviceBinder.getServiceIfBound(); if (trackRecordingService == null) { // Fall back to alternative check method. return dataHub.isRecording(); @@ -177,10 +152,10 @@ public class MyTracks extends TabActivity implements OnTouchListener { } providerUtils = MyTracksProviderUtils.Factory.get(this); - sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0); - dataHub = new TrackDataHub(this, sharedPreferences, providerUtils); + dataHub = new TrackDataHub(this, providerUtils); menuManager = new MenuManager(this); dialogManager = new DialogManager(this); + serviceBinder = TrackRecordingServiceBinder.getInstance(this); // The volume we want to control is the Text-To-Speech volume int volumeStream = @@ -230,7 +205,6 @@ public class MyTracks extends TabActivity implements OnTouchListener { dataHub.destroy(); - tryUnbindTrackRecordingService(); super.onDestroy(); } @@ -250,7 +224,7 @@ public class MyTracks extends TabActivity implements OnTouchListener { // 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"); - tryUnbindTrackRecordingService(); + serviceBinder.unbindService(); super.onPause(); } @@ -259,7 +233,7 @@ public class MyTracks extends TabActivity implements OnTouchListener { // Called when the current activity is being displayed or re-displayed // to the user. Log.d(TAG, "MyTracks.onResume"); - tryBindTrackRecordingService(); + serviceBinder.bindService(serviceBindCallback); super.onResume(); } @@ -271,8 +245,7 @@ public class MyTracks extends TabActivity implements OnTouchListener { // Ensure that service is running if we're supposed to be recording if (dataHub.isRecording()) { - Intent startIntent = new Intent(this, TrackRecordingService.class); - startService(startIntent); + serviceBinder.startService(); } Intent intent = getIntent(); @@ -445,6 +418,7 @@ public class MyTracks extends TabActivity implements OnTouchListener { * @throws RemoteException If the call on the service failed. */ public long insertWaypoint(WaypointCreationRequest request) throws RemoteException { + ITrackRecordingService trackRecordingService = serviceBinder.getServiceIfBound(); if (trackRecordingService == null) { throw new IllegalStateException("The recording service is not bound."); } @@ -485,14 +459,16 @@ public class MyTracks extends TabActivity implements OnTouchListener { * Starts the track recording service (if not already running) and binds to * it. Starts recording a new track. */ - public void startRecording() { - if (trackRecordingService == null) { + void startRecording() { + synchronized (serviceBinder) { startNewTrackRequested = true; - Intent startIntent = new Intent(this, TrackRecordingService.class); - startService(startIntent); - tryBindTrackRecordingService(); - } else { - startRecordingNewTrack(trackRecordingService); + serviceBinder.startService(); + + // Binding was already requested before, it either already happened + // (in which case running the callback manually triggers the actual recording start) + // or it will happen in the future + // (in which case running the callback now will have no effect). + serviceBindCallback.run(); } } @@ -501,8 +477,10 @@ public class MyTracks extends TabActivity implements OnTouchListener { * "Stopped recording" and pop up the Track Details activity. */ public void stopRecording() { + ITrackRecordingService trackRecordingService = serviceBinder.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(); @@ -519,14 +497,10 @@ public class MyTracks extends TabActivity implements OnTouchListener { startActivity(intent); } } - tryUnbindTrackRecordingService(); - try { - stopService(new Intent(MyTracks.this, TrackRecordingService.class)); - } catch (SecurityException e) { - Log.e(TAG, "Encountered a security exception when trying to stop service.", e); - } - trackRecordingService = null; + + serviceBinder.stopService(); } + void clearSelectedTrack() { dataHub.unloadCurrentTrack(); } @@ -535,54 +509,10 @@ public class MyTracks extends TabActivity implements OnTouchListener { return dataHub.getSelectedTrackId(); } - /** - * Binds to track recording service if it is running. - */ - private void tryBindTrackRecordingService() { - Log.d(TAG, - "MyTracks: Trying to bind to track recording service..."); - bindService(new Intent(this, TrackRecordingService.class), - serviceConnection, 0); - Log.d(TAG, "MyTracks: ...bind finished!"); - isBound = true; - } - - /** - * Tries to unbind the track recording service. Catches exception silently in - * case service is not registered anymore. - */ - private void tryUnbindTrackRecordingService() { - if (isBound) { - Log.d(TAG, "MyTracks: Trying to unbind from track recording service..."); - try { - unbindService(serviceConnection); - Log.d(TAG, "MyTracks: ...unbind finished!"); - } catch (IllegalArgumentException e) { - Log.d(TAG, "MyTracks: Tried unbinding, but service was not registered.", e); - } - isBound = false; - } - } - public DialogManager getDialogManager() { return dialogManager; } - // @VisibleForTesting - long getRecordingTrackId() { - return sharedPreferences.getLong(getString(R.string.recording_track_key), -1); - } - - // @VisibleForTesting - SharedPreferences getSharedPreferences() { - return sharedPreferences; - } - - // @VisibleForTesting - ITrackRecordingService getTrackRecordingService() { - return trackRecordingService; - } - public TrackDataHub getDataHub() { return dataHub; } @@ -593,8 +523,4 @@ public class MyTracks extends TabActivity implements OnTouchListener { public static MyTracks getInstance() { return instance; } - - public static void clearInstance() { - instance = null; - } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java b/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java index 5302ad80b..46f628e28 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java @@ -18,6 +18,7 @@ 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.sensors.SensorUtils; import com.google.android.maps.mytracks.R; import com.google.protobuf.InvalidProtocolBufferException; @@ -45,6 +46,7 @@ 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. @@ -79,6 +81,8 @@ public class SensorStateActivity extends Activity { setContentView(R.layout.sensor_state); + serviceBinder = TrackRecordingServiceBinder.getInstance(this); + serviceBinder.bindService(stateUpdater); updateState(); } @@ -97,11 +101,13 @@ public class SensorStateActivity extends Activity { timer = null; } - protected void updateState() { - MyTracks mt = MyTracks.getInstance(); + @Override + protected void onDestroy() { + serviceBinder.unbindService(); + } - ITrackRecordingService service = - mt == null ? null : mt.getTrackRecordingService(); + protected void updateState() { + ITrackRecordingService service = serviceBinder.getServiceIfBound(); if (service == null) { Log.d(Constants.TAG, "Could not get track recording service."); updateSensorState(Sensor.SensorState.NONE); diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java index dc00b599e..fcb1a8709 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java @@ -22,6 +22,7 @@ import static com.google.android.apps.mytracks.Constants.MAX_LOCATION_AGE_MS; import static com.google.android.apps.mytracks.Constants.MAX_NETWORK_AGE_MS; import static com.google.android.apps.mytracks.Constants.TAG; +import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.DataSourceManager.DataSourceListener; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.DoubleBufferedLocationFactory; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator; @@ -195,10 +196,17 @@ public class TrackDataHub { /** * Default constructor. */ - public TrackDataHub(Context ctx, SharedPreferences preferences, + public TrackDataHub(Context ctx, MyTracksProviderUtils providerUtils) { + this(ctx, ctx.getSharedPreferences(Constants.SETTINGS_NAME, 0), providerUtils); + } + + /** + * Intermediate constructor, used only for passing preferences into arguments. + */ + private TrackDataHub(Context ctx, SharedPreferences preferences, MyTracksProviderUtils providerUtils) { this(ctx, new DataSourcesWrapperImpl(ctx, preferences), new TrackDataListeners(), - preferences, providerUtils, TARGET_DISPLAYED_TRACK_POINTS); + preferences, providerUtils, TARGET_DISPLAYED_TRACK_POINTS); } /** 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 6dd5a76f3..e7de8e82d 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java @@ -785,7 +785,7 @@ public class TrackRecordingService extends Service implements LocationListener { if (intent != null && intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) { Log.d(TAG, "TrackRecordingService: requested resume"); - + // Make sure that the current track exists and is fresh enough. if (recordingTrack == null || !shouldResumeTrack(recordingTrack)) { Log.i(TAG, @@ -796,7 +796,7 @@ public class TrackRecordingService extends Service implements LocationListener { stopSelfResult(startId); return; } - + Log.i(TAG, "TrackRecordingService: resuming"); } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingServiceBinder.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingServiceBinder.java new file mode 100644 index 000000000..67d093d0f --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingServiceBinder.java @@ -0,0 +1,203 @@ +/* + * 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 helper for managing 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) { + try { + for (Runnable callback : pendingBindCallbacks.keySet()) { + if (callback != null) { + callback.run(); + } + } + } finally { + pendingBindCallbacks.clear(); + TrackRecordingServiceBinder.this.trackRecordingService = trackRecordingService; + } + } + } + + @Override + public void onServiceDisconnected(ComponentName className) { + Log.d(TAG, "Service now disconnected."); + synchronized (TrackRecordingServiceBinder.this) { + trackRecordingService = null; + } + } + }; + + /** Pointer 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) { + Context applicationContext = context.getApplicationContext(); + instance = new TrackRecordingServiceBinder(applicationContext); + } + return instance; + } + + /** + * Internal constructor. + * + * @param applicationContext the current application (not activity) context + */ + private TrackRecordingServiceBinder(Context applicationContext) { + this.applicationContext = applicationContext; + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksTest.java index 7c5151944..5615737e5 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksTest.java @@ -36,56 +36,48 @@ import java.util.concurrent.atomic.AtomicBoolean; * @author Bartlomiej Niechwiej */ public class MyTracksTest extends ActivityInstrumentationTestCase2{ + private SharedPreferences sharedPreferences; + public MyTracksTest() { super(MyTracks.class); } - @Override - protected void setUp() throws Exception { - super.setUp(); - MyTracks.clearInstance(); - assertNull(MyTracks.getInstance()); - } - @Override protected void tearDown() throws Exception { clearSelectedAndRecordingTracks(); waitForIdle(); + super.tearDown(); } - + public void testInitialization_mainAction() { // Make sure we can start MyTracks and the activity doesn't start recording. - assertNotNull(getActivity()); - assertNotNull(MyTracks.getInstance()); - assertNotNull(getActivity().getSharedPreferences()); - + assertInitialized(); + // Check if not recording. assertFalse(getActivity().isRecording()); - assertEquals(-1, getActivity().getRecordingTrackId()); - long selectedTrackId = getActivity().getSharedPreferences().getLong( + assertEquals(-1, getRecordingTrackId()); + long selectedTrackId = getSharedPreferences().getLong( getActivity().getString(R.string.selected_track_key), -1); assertEquals(selectedTrackId, getActivity().getSelectedTrackId()); } - + public void testInitialization_viewActionWithNoData() { // Simulate start with ACTION_VIEW intent. Intent startIntent = new Intent(); startIntent.setAction(Intent.ACTION_VIEW); setActivityIntent(startIntent); - - assertNotNull(getActivity()); - assertNotNull(MyTracks.getInstance()); - assertNotNull(getActivity().getSharedPreferences()); - + + assertInitialized(); + // Check if not recording. assertFalse(getActivity().isRecording()); - assertEquals(-1, getActivity().getRecordingTrackId()); - long selectedTrackId = getActivity().getSharedPreferences().getLong( + assertEquals(-1, getRecordingTrackId()); + long selectedTrackId = getSharedPreferences().getLong( getActivity().getString(R.string.selected_track_key), -1); assertEquals(selectedTrackId, getActivity().getSelectedTrackId()); } - + public void testInitialization_viewActionWithValidData() throws Exception { // Simulate start with ACTION_VIEW intent. Intent startIntent = new Intent(); @@ -93,24 +85,22 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{ Uri uri = Uri.fromFile(File.createTempFile("valid", ".gpx")); // TODO: Add a valid GPX. - + startIntent.setData(uri); setActivityIntent(startIntent); - - assertNotNull(getActivity()); - assertNotNull(MyTracks.getInstance()); - assertNotNull(getActivity().getSharedPreferences()); - + + assertInitialized(); + // Check if not recording. assertFalse(getActivity().isRecording()); - assertEquals(-1, getActivity().getRecordingTrackId()); - long selectedTrackId = getActivity().getSharedPreferences().getLong( + assertEquals(-1, getRecordingTrackId()); + long selectedTrackId = getSharedPreferences().getLong( getActivity().getString(R.string.selected_track_key), -1); assertEquals(selectedTrackId, getActivity().getSelectedTrackId()); - - // TODO: Finish this test. + + // TODO: Finish this test. } - + public void testInitialization_viewActionWithInvalidData() throws Exception { // Simulate start with ACTION_VIEW intent. Intent startIntent = new Intent(); @@ -118,34 +108,29 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{ Uri uri = Uri.fromFile(File.createTempFile("invalid", ".gpx")); startIntent.setData(uri); setActivityIntent(startIntent); - - assertNotNull(getActivity()); - assertNotNull(MyTracks.getInstance()); - assertNotNull(getActivity().getSharedPreferences()); - + + assertInitialized(); + // Check if not recording. assertFalse(getActivity().isRecording()); - assertEquals(-1, getActivity().getRecordingTrackId()); - long selectedTrackId = getActivity().getSharedPreferences().getLong( + assertEquals(-1, getRecordingTrackId()); + long selectedTrackId = getSharedPreferences().getLong( getActivity().getString(R.string.selected_track_key), -1); assertEquals(selectedTrackId, getActivity().getSelectedTrackId()); - + // TODO: Finish this test. } - + public void testRecording_startAndStop() throws Exception { - // Make sure we can start MyTracks and the activity doesn't start recording. - assertNotNull(getActivity()); - assertNotNull(MyTracks.getInstance()); - assertNotNull(getActivity().getSharedPreferences()); + assertInitialized(); // Check if not recording. clearSelectedAndRecordingTracks(); waitForIdle(); assertFalse(getActivity().isRecording()); - assertEquals(-1, getActivity().getRecordingTrackId()); - long selectedTrackId = getActivity().getSharedPreferences().getLong( + assertEquals(-1, getRecordingTrackId()); + long selectedTrackId = getSharedPreferences().getLong( getActivity().getString(R.string.selected_track_key), -1); assertEquals(selectedTrackId, getActivity().getSelectedTrackId()); @@ -153,16 +138,16 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{ getActivity().startRecording(); long recordingTrackId = awaitRecordingStatus(5000, true); assertTrue(recordingTrackId >= 0); - + // Wait until we are done and make sure that selectedTrack = recordingTrack. waitForIdle(); - assertEquals(recordingTrackId, getActivity().getSharedPreferences().getLong( + assertEquals(recordingTrackId, getSharedPreferences().getLong( getActivity().getString(R.string.recording_track_key), -1)); - selectedTrackId = getActivity().getSharedPreferences().getLong( + selectedTrackId = getSharedPreferences().getLong( getActivity().getString(R.string.selected_track_key), -1); assertEquals(recordingTrackId, selectedTrackId); assertEquals(selectedTrackId, getActivity().getSelectedTrackId()); - + // Watch for MyTracksDetails activity. ActivityMonitor monitor = getInstrumentation().addMonitor( TrackDetails.class.getName(), null, false); @@ -170,14 +155,14 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{ // Now, stop the track and make sure that it is still selected, but // no longer recording. getActivity().stopRecording(); - + // Check if we got back MyTracksDetails activity. Activity activity = getInstrumentation().waitForMonitor(monitor); assertTrue(activity instanceof TrackDetails); - + // TODO: Update track name and other properties and test if they were // properly saved. - + // Simulate a click on Save button. Button save = (Button) activity.findViewById(R.id.trackdetails_save); save.performClick(); @@ -185,13 +170,18 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{ // Check the remaining properties. recordingTrackId = awaitRecordingStatus(5000, false); assertEquals(-1, recordingTrackId); - assertEquals(recordingTrackId, getActivity().getRecordingTrackId()); - assertEquals(recordingTrackId, getActivity().getSharedPreferences().getLong( + assertEquals(recordingTrackId, getRecordingTrackId()); + assertEquals(recordingTrackId, getSharedPreferences().getLong( getActivity().getString(R.string.recording_track_key), -1)); // Make sure this is the same track as the last recording track ID. assertEquals(selectedTrackId, getActivity().getSelectedTrackId()); } - + + private void assertInitialized() { + assertNotNull(getActivity()); + assertNotNull(MyTracks.getInstance()); + } + /** * Waits until the UI thread becomes idle. */ @@ -216,13 +206,13 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{ } /** - * Clears {selected,recording}TrackId in the {@link SharedPreferences}. + * Clears {selected,recording}TrackId in the {@link getSharedPreferences()}. */ private void clearSelectedAndRecordingTracks() { - Editor editor = getActivity().getSharedPreferences().edit(); + Editor editor = getSharedPreferences().edit(); editor.putLong(getActivity().getString(R.string.selected_track_key), -1); editor.putLong(getActivity().getString(R.string.recording_track_key), -1); - + editor.clear(); editor.commit(); } @@ -245,6 +235,17 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2{ } waitForIdle(); assertEquals(isRecording, getActivity().isRecording()); - return getActivity().getRecordingTrackId(); + return getRecordingTrackId(); + } + + private long getRecordingTrackId() { + return getSharedPreferences().getLong(getActivity().getString(R.string.recording_track_key), -1); + } + + private SharedPreferences getSharedPreferences() { + if (sharedPreferences == null) { + sharedPreferences = getActivity().getSharedPreferences(Constants.SETTINGS_NAME, 0); + } + return sharedPreferences; } }