diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java index 23e837b8e..62117a82c 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java @@ -24,6 +24,7 @@ import com.google.android.apps.mytracks.content.WaypointsColumns; import com.google.android.apps.mytracks.services.StatusAnnouncerFactory; import com.google.android.apps.mytracks.stats.DoubleBuffer; import com.google.android.apps.mytracks.stats.TripStatisticsBuilder; +import com.google.android.apps.mytracks.util.ApiFeatures; import com.google.android.apps.mytracks.util.MyTracksUtils; import com.google.android.apps.mytracks.util.UnitConversions; import com.google.android.maps.mytracks.R; @@ -243,7 +244,9 @@ public class ChartActivity extends Activity implements providerUtils = MyTracksProviderUtils.Factory.get(this); // The volume we want to control is the Text-To-Speech volume - setVolumeControlStream(StatusAnnouncerFactory.getVolumeStream()); + int volumeStream = + new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream(); + setVolumeControlStream(volumeStream); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.mytracks_elevation); diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java index 853f36494..7bc547edd 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java @@ -32,6 +32,7 @@ import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat; import com.google.android.apps.mytracks.services.ITrackRecordingService; import com.google.android.apps.mytracks.services.StatusAnnouncerFactory; import com.google.android.apps.mytracks.services.TrackRecordingService; +import com.google.android.apps.mytracks.util.ApiFeatures; import com.google.android.apps.mytracks.util.FileUtils; import com.google.android.maps.mytracks.R; @@ -258,7 +259,9 @@ public class MyTracks extends TabActivity implements OnTouchListener, menuManager = new MenuManager(this); // The volume we want to control is the Text-To-Speech volume - setVolumeControlStream(StatusAnnouncerFactory.getVolumeStream()); + int volumeStream = + new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream(); + setVolumeControlStream(volumeStream); // We don't need a window title bar: requestWindowFeature(Window.FEATURE_NO_TITLE); diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java index 7de3634b7..ce961d7a3 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java @@ -23,6 +23,7 @@ import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.WaypointsColumns; import com.google.android.apps.mytracks.services.StatusAnnouncerFactory; import com.google.android.apps.mytracks.stats.TripStatistics; +import com.google.android.apps.mytracks.util.ApiFeatures; import com.google.android.apps.mytracks.util.MyTracksUtils; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapActivity; @@ -282,7 +283,9 @@ public class MyTracksMap extends MapActivity super.onCreate(bundle); // The volume we want to control is the Text-To-Speech volume - setVolumeControlStream(StatusAnnouncerFactory.getVolumeStream()); + int volumeStream = + new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream(); + setVolumeControlStream(volumeStream); providerUtils = MyTracksProviderUtils.Factory.get(this); diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java index 86a88ac66..201578f5e 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java @@ -61,7 +61,10 @@ public class MyTracksSettings extends PreferenceActivity { super.onCreate(icicle); // The volume we want to control is the Text-To-Speech volume - setVolumeControlStream(StatusAnnouncerFactory.getVolumeStream()); + ApiFeatures apiFeatures = ApiFeatures.getInstance(); + int volumeStream = + new StatusAnnouncerFactory(apiFeatures).getVolumeStream(); + setVolumeControlStream(volumeStream); // Tell it where to read/write preferences PreferenceManager preferenceManager = getPreferenceManager(); @@ -69,7 +72,7 @@ public class MyTracksSettings extends PreferenceActivity { preferenceManager.setSharedPreferencesMode(0); // Set up automatic preferences backup - backupListener = BackupPreferencesListener.create(this); + backupListener = BackupPreferencesListener.create(this, apiFeatures); preferences = preferenceManager.getSharedPreferences(); preferences.registerOnSharedPreferenceChangeListener(backupListener); @@ -94,7 +97,7 @@ public class MyTracksSettings extends PreferenceActivity { updatePreferenceUnits(metricUnitsPreference.isChecked()); // Disable TTS announcement preference if not available - if (!ApiFeatures.hasTextToSpeech()) { + if (!apiFeatures.hasTextToSpeech()) { IntegerListPreference announcementFrequency = (IntegerListPreference) findPreference( getString(R.string.announcement_frequency_key)); diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java index 76e7c5d76..403190a6c 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java @@ -21,6 +21,7 @@ import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.TracksColumns; import com.google.android.apps.mytracks.content.WaypointsColumns; import com.google.android.apps.mytracks.services.StatusAnnouncerFactory; +import com.google.android.apps.mytracks.util.ApiFeatures; import com.google.android.maps.mytracks.R; import android.app.Activity; @@ -143,7 +144,9 @@ public class StatsActivity extends Activity super.onCreate(savedInstanceState); // The volume we want to control is the Text-To-Speech volume - setVolumeControlStream(StatusAnnouncerFactory.getVolumeStream()); + int volumeStream = + new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream(); + setVolumeControlStream(volumeStream); // We don't need a window title bar: requestWindowFeature(Window.FEATURE_NO_TITLE); diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListener.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListener.java index ee1dc7415..51e9439ea 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListener.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListener.java @@ -15,7 +15,6 @@ */ package com.google.android.apps.mytracks.io.backup; -import com.google.android.apps.mytracks.MyTracksConstants; import com.google.android.apps.mytracks.util.ApiFeatures; import android.app.backup.BackupManager; @@ -67,8 +66,9 @@ public abstract class BackupPreferencesListener /** * Creates and returns a proper instance of the listener for this device. */ - public static BackupPreferencesListener create(Context context) { - if (ApiFeatures.hasBackup()) { + public static BackupPreferencesListener create( + Context context, ApiFeatures apiFeatures) { + if (apiFeatures.hasBackup()) { return new BackupPreferencesListenerImpl(context); } else { return new DummyBackupPreferencesListener(); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/SignalStrengthTaskFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/SignalStrengthTaskFactory.java new file mode 100644 index 000000000..844b48b95 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/services/SignalStrengthTaskFactory.java @@ -0,0 +1,48 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.services; + +import com.google.android.apps.mytracks.MyTracksConstants; +import com.google.android.apps.mytracks.util.ApiFeatures; + +import android.content.Context; +import android.util.Log; + +/** + * Factory for producing a proper {@link SignalStrengthTask} according to the + * current API level. + * + * @author Rodrigo Damazio + */ +public class SignalStrengthTaskFactory { + private final boolean hasModernSignalStrength; + + SignalStrengthTaskFactory(ApiFeatures apiFeatures) { + this.hasModernSignalStrength = apiFeatures.hasModernSignalStrength(); + } + + public PeriodicTask create(Context context) { + if (hasModernSignalStrength) { + Log.d(MyTracksConstants.TAG, + "TrackRecordingService using modern signal strength api."); + return new SignalStrengthTaskModern(context); + } else { + Log.w(MyTracksConstants.TAG, + "TrackRecordingService using legacy signal strength api."); + return new SignalStrengthTask(context); + } + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerFactory.java index 2cd97fa10..c7ab903fd 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerFactory.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerFactory.java @@ -28,13 +28,19 @@ import android.media.AudioManager; */ public class StatusAnnouncerFactory { + private final boolean hasTts; + + public StatusAnnouncerFactory(ApiFeatures apiFeatures) { + this.hasTts = apiFeatures.hasTextToSpeech(); + } + /** * Creates a periodic task which does voice announcements. * * @return the task, or null if announcements are not supported */ - public static PeriodicTask create(Context context) { - if (ApiFeatures.hasTextToSpeech()) { + public PeriodicTask create(Context context) { + if (hasTts) { return new StatusAnnouncerTask(context); } else { return null; @@ -45,13 +51,11 @@ public class StatusAnnouncerFactory { * Returns the appropriate volume stream for controlling announcement * volume. */ - public static int getVolumeStream() { - if (ApiFeatures.hasTextToSpeech()) { + public int getVolumeStream() { + if (hasTts) { return StatusAnnouncerTask.getVolumeStream(); } else { return AudioManager.USE_DEFAULT_STREAM_TYPE; } } - - private StatusAnnouncerFactory() {} } diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerTask.java b/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerTask.java index 600d8dc27..158a978ae 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerTask.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerTask.java @@ -83,7 +83,7 @@ public class StatusAnnouncerTask implements PeriodicTask { /** * Called when the TTS engine is initialized. */ - public void onTtsInit(int status) { + private void onTtsInit(int status) { Log.i(MyTracksConstants.TAG, "TrackRecordingService.TTS init: " + status); this.ready = (status == TextToSpeech.SUCCESS); 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 9afacd19a..1a3da0b1b 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java @@ -635,17 +635,10 @@ public class TrackRecordingService extends Service implements LocationListener { locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); splitManager = new SplitManager(this); - PeriodicTask signalStrengthTask; - if (ApiFeatures.hasModernSignalStrength()) { - Log.d(MyTracksConstants.TAG, - "TrackRecordingService using modern signal strength api."); - signalStrengthTask = new SignalStrengthTaskModern(this); - } else { - Log.w(MyTracksConstants.TAG, - "TrackRecordingService using legacy signal strength api."); - signalStrengthTask = new SignalStrengthTask(this); - } - signalManager = new TaskExecuterManager(-1, signalStrengthTask, this); + SignalStrengthTaskFactory strengthTaskFactory = + new SignalStrengthTaskFactory(ApiFeatures.getInstance()); + signalManager = + new TaskExecuterManager(-1, strengthTaskFactory.create(this), this); prefManager = new PreferenceManager(this); prefManager.onSharedPreferenceChanged(null); @@ -666,7 +659,7 @@ public class TrackRecordingService extends Service implements LocationListener { } showNotification(); } - + /** * Creates an {@link Executer} and schedules {@class SafeStatusAnnouncerTask}. * The announcer requires a TTS service and user should have enabled @@ -675,7 +668,9 @@ public class TrackRecordingService extends Service implements LocationListener { private void setUpAnnouncer() { if (announcementFrequency != -1) { if (announcementExecuter == null) { - PeriodicTask announcer = StatusAnnouncerFactory.create(this); + StatusAnnouncerFactory statusAnnouncerFactory = + new StatusAnnouncerFactory(ApiFeatures.getInstance()); + PeriodicTask announcer = statusAnnouncerFactory.create(this); if (announcer == null) return; announcementExecuter = new PeriodicTaskExecuter(announcer, this); diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java index 096c935b9..b6a4e02fd 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java @@ -30,18 +30,42 @@ public class ApiFeatures { */ public static final int ANDROID_API_LEVEL = Integer.parseInt(Build.VERSION.SDK); + private static ApiFeatures instance; + + /** + * Returns the singleton instance of this class. + */ + public static ApiFeatures getInstance() { + if (instance == null) { + instance = new ApiFeatures(); + } + return instance; + } + + /** + * Injects a specific singleton instance, to be used for unit tests. + */ + public static void injectInstance(ApiFeatures instance) { + ApiFeatures.instance = instance; + } + + /** + * Allow subclasses for mocking, but no direct instantiation. + */ + protected ApiFeatures() {} + /** * Returns whether cloud backup (a.k.a. Froyo backup) is available. */ - public static boolean hasBackup() { - return ANDROID_API_LEVEL >= 8; + public boolean hasBackup() { + return getApiLevel() >= 8; } /** * Returns whether text-to-speech is available. */ - public static boolean hasTextToSpeech() { - if (ANDROID_API_LEVEL < 4) return false; + public boolean hasTextToSpeech() { + if (getApiLevel() < 4) return false; try { Class.forName("android.speech.tts.TextToSpeech"); @@ -54,9 +78,12 @@ public class ApiFeatures { return true; } - public static boolean hasModernSignalStrength() { - return ANDROID_API_LEVEL >= 7; + public boolean hasModernSignalStrength() { + return getApiLevel() >= 7; } - private ApiFeatures() {} + // Visible for testing. + protected int getApiLevel() { + return ANDROID_API_LEVEL; + } } diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/SignalStrengthTaskFactoryTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/SignalStrengthTaskFactoryTest.java new file mode 100644 index 000000000..73da7aab9 --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/SignalStrengthTaskFactoryTest.java @@ -0,0 +1,69 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.services; + +import com.google.android.apps.mytracks.util.ApiFeatures; + +import android.test.AndroidTestCase; + +/** + * Tests for {@link SignalStrengthTaskFactoryTest}. + * These tests require Eclair+ (API level 7) to run. + * + * @author Rodrigo Damazio + */ +public class SignalStrengthTaskFactoryTest extends AndroidTestCase { + private static class MockApiFeatures extends ApiFeatures { + private boolean hasModernStrength; + + public void setHasModernSignalStrength(boolean hasModernStrength) { + this.hasModernStrength = hasModernStrength; + } + + @Override + public boolean hasModernSignalStrength() { + return hasModernStrength; + } + } + + private MockApiFeatures apiFeatures; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + apiFeatures = new MockApiFeatures(); + } + + public void testCreate() { + apiFeatures.setHasModernSignalStrength(true); + SignalStrengthTaskFactory factory = + new SignalStrengthTaskFactory(apiFeatures); + + PeriodicTask task = factory.create(getContext()); + assertTrue(task.getClass().getName(), + task instanceof SignalStrengthTaskModern); + } + + public void testCreate_legacy() { + apiFeatures.setHasModernSignalStrength(false); + SignalStrengthTaskFactory factory = + new SignalStrengthTaskFactory(apiFeatures); + + PeriodicTask task = factory.create(getContext()); + assertTrue(task.getClass().getName(), task instanceof SignalStrengthTask); + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/StatusAnnouncerFactoryTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/StatusAnnouncerFactoryTest.java new file mode 100644 index 000000000..ef7b4ccd8 --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/StatusAnnouncerFactoryTest.java @@ -0,0 +1,88 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.services; + +import com.google.android.apps.mytracks.util.ApiFeatures; + +import android.media.AudioManager; +import android.speech.tts.TextToSpeech; +import android.test.AndroidTestCase; + +/** + * Tests for {@link StatusAnnouncerFactory}. + * These tests require Donut+ to run. + * + * @author Rodrigo Damazio + */ +public class StatusAnnouncerFactoryTest extends AndroidTestCase { + + + private class MockApiFeatures extends ApiFeatures { + private boolean hasTts; + + public void setHasTextToSpeech(boolean hasTts) { + this.hasTts = hasTts; + } + + @Override + public boolean hasTextToSpeech() { + return hasTts; + } + } + + private MockApiFeatures apiFeatures; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + apiFeatures = new MockApiFeatures(); + } + + public void testCreate() { + apiFeatures.setHasTextToSpeech(true); + StatusAnnouncerFactory factory = + new StatusAnnouncerFactory(apiFeatures); + PeriodicTask task = factory.create(getContext()); + assertTrue(task instanceof StatusAnnouncerTask); + } + + public void testCreate_notAvailable() { + apiFeatures.setHasTextToSpeech(false); + StatusAnnouncerFactory factory = + new StatusAnnouncerFactory(apiFeatures); + PeriodicTask task = factory.create(getContext()); + assertNull(task); + } + + public void testGetVolumeStream() { + apiFeatures.setHasTextToSpeech(true); + StatusAnnouncerFactory factory = + new StatusAnnouncerFactory(apiFeatures); + assertEquals( + TextToSpeech.Engine.DEFAULT_STREAM, + factory.getVolumeStream()); + } + + public void testGetVolumeStream_notAvailable() { + apiFeatures.setHasTextToSpeech(false); + StatusAnnouncerFactory factory = + new StatusAnnouncerFactory(apiFeatures); + assertEquals( + AudioManager.USE_DEFAULT_STREAM_TYPE, + factory.getVolumeStream()); + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/util/ApiFeaturesTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/util/ApiFeaturesTest.java new file mode 100644 index 000000000..1f120cbf2 --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/util/ApiFeaturesTest.java @@ -0,0 +1,78 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.util; + +import junit.framework.TestCase; + +/** + * Tests the API feature detection code in {@link ApiFeatures}. + * This test requires Froyo+ to run. + * + * @author Rodrigo Damazio + */ +public class ApiFeaturesTest extends TestCase { + + private TestableApiFeatures features; + + private class TestableApiFeatures extends ApiFeatures { + private int apiLevel; + + public void setApiLevel(int apiLevel) { + this.apiLevel = apiLevel; + } + + @Override + protected int getApiLevel() { + return apiLevel; + } + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + features = new TestableApiFeatures(); + } + + public void testHasBackup() { + for (int i = 3; i <= 7; i++) { + features.setApiLevel(i); + assertFalse(features.hasBackup()); + } + features.setApiLevel(8); + assertTrue(features.hasBackup()); + } + + public void testHasTextToSpeech() { + features.setApiLevel(3); + assertFalse(features.hasTextToSpeech()); + for (int i = 4; i <= 8; i++) { + features.setApiLevel(i); + assertTrue(features.hasTextToSpeech()); + } + } + + public void testHasModernSignalStrength() { + for (int i = 3; i <= 6; i++) { + features.setApiLevel(i); + assertFalse(features.hasModernSignalStrength()); + } + features.setApiLevel(7); + assertTrue(features.hasModernSignalStrength()); + features.setApiLevel(8); + assertTrue(features.hasModernSignalStrength()); + } +}