Addressing review suggestions and adding tests.

This commit is contained in:
Rodrigo Damazio
2010-10-13 21:53:16 -03:00
parent dd9dd8ee81
commit 24adee3d54
14 changed files with 361 additions and 37 deletions
@@ -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);
@@ -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);
@@ -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);
@@ -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));
@@ -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);
@@ -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();
@@ -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);
}
}
}
@@ -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() {}
}
@@ -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);
@@ -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);
@@ -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;
}
}
@@ -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);
}
}
@@ -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());
}
}
@@ -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());
}
}