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.services.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.stats.DoubleBuffer; import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder; 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.MyTracksUtils;
import com.google.android.apps.mytracks.util.UnitConversions; import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R; import com.google.android.maps.mytracks.R;
@@ -243,7 +244,9 @@ public class ChartActivity extends Activity implements
providerUtils = MyTracksProviderUtils.Factory.get(this); providerUtils = MyTracksProviderUtils.Factory.get(this);
// The volume we want to control is the Text-To-Speech volume // 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); requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.mytracks_elevation); 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.ITrackRecordingService;
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory; import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.services.TrackRecordingService; 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.apps.mytracks.util.FileUtils;
import com.google.android.maps.mytracks.R; import com.google.android.maps.mytracks.R;
@@ -258,7 +259,9 @@ public class MyTracks extends TabActivity implements OnTouchListener,
menuManager = new MenuManager(this); menuManager = new MenuManager(this);
// The volume we want to control is the Text-To-Speech volume // 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: // We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE); 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.content.WaypointsColumns;
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory; import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.stats.TripStatistics; 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.apps.mytracks.util.MyTracksUtils;
import com.google.android.maps.GeoPoint; import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity; import com.google.android.maps.MapActivity;
@@ -282,7 +283,9 @@ public class MyTracksMap extends MapActivity
super.onCreate(bundle); super.onCreate(bundle);
// The volume we want to control is the Text-To-Speech volume // 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); providerUtils = MyTracksProviderUtils.Factory.get(this);
@@ -61,7 +61,10 @@ public class MyTracksSettings extends PreferenceActivity {
super.onCreate(icicle); super.onCreate(icicle);
// The volume we want to control is the Text-To-Speech volume // 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 // Tell it where to read/write preferences
PreferenceManager preferenceManager = getPreferenceManager(); PreferenceManager preferenceManager = getPreferenceManager();
@@ -69,7 +72,7 @@ public class MyTracksSettings extends PreferenceActivity {
preferenceManager.setSharedPreferencesMode(0); preferenceManager.setSharedPreferencesMode(0);
// Set up automatic preferences backup // Set up automatic preferences backup
backupListener = BackupPreferencesListener.create(this); backupListener = BackupPreferencesListener.create(this, apiFeatures);
preferences = preferenceManager.getSharedPreferences(); preferences = preferenceManager.getSharedPreferences();
preferences.registerOnSharedPreferenceChangeListener(backupListener); preferences.registerOnSharedPreferenceChangeListener(backupListener);
@@ -94,7 +97,7 @@ public class MyTracksSettings extends PreferenceActivity {
updatePreferenceUnits(metricUnitsPreference.isChecked()); updatePreferenceUnits(metricUnitsPreference.isChecked());
// Disable TTS announcement preference if not available // Disable TTS announcement preference if not available
if (!ApiFeatures.hasTextToSpeech()) { if (!apiFeatures.hasTextToSpeech()) {
IntegerListPreference announcementFrequency = IntegerListPreference announcementFrequency =
(IntegerListPreference) findPreference( (IntegerListPreference) findPreference(
getString(R.string.announcement_frequency_key)); 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.TracksColumns;
import com.google.android.apps.mytracks.content.WaypointsColumns; import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory; import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.maps.mytracks.R; import com.google.android.maps.mytracks.R;
import android.app.Activity; import android.app.Activity;
@@ -143,7 +144,9 @@ public class StatsActivity extends Activity
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// The volume we want to control is the Text-To-Speech volume // 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: // We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_NO_TITLE);
@@ -15,7 +15,6 @@
*/ */
package com.google.android.apps.mytracks.io.backup; package com.google.android.apps.mytracks.io.backup;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.util.ApiFeatures; import com.google.android.apps.mytracks.util.ApiFeatures;
import android.app.backup.BackupManager; 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. * Creates and returns a proper instance of the listener for this device.
*/ */
public static BackupPreferencesListener create(Context context) { public static BackupPreferencesListener create(
if (ApiFeatures.hasBackup()) { Context context, ApiFeatures apiFeatures) {
if (apiFeatures.hasBackup()) {
return new BackupPreferencesListenerImpl(context); return new BackupPreferencesListenerImpl(context);
} else { } else {
return new DummyBackupPreferencesListener(); 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 { public class StatusAnnouncerFactory {
private final boolean hasTts;
public StatusAnnouncerFactory(ApiFeatures apiFeatures) {
this.hasTts = apiFeatures.hasTextToSpeech();
}
/** /**
* Creates a periodic task which does voice announcements. * Creates a periodic task which does voice announcements.
* *
* @return the task, or null if announcements are not supported * @return the task, or null if announcements are not supported
*/ */
public static PeriodicTask create(Context context) { public PeriodicTask create(Context context) {
if (ApiFeatures.hasTextToSpeech()) { if (hasTts) {
return new StatusAnnouncerTask(context); return new StatusAnnouncerTask(context);
} else { } else {
return null; return null;
@@ -45,13 +51,11 @@ public class StatusAnnouncerFactory {
* Returns the appropriate volume stream for controlling announcement * Returns the appropriate volume stream for controlling announcement
* volume. * volume.
*/ */
public static int getVolumeStream() { public int getVolumeStream() {
if (ApiFeatures.hasTextToSpeech()) { if (hasTts) {
return StatusAnnouncerTask.getVolumeStream(); return StatusAnnouncerTask.getVolumeStream();
} else { } else {
return AudioManager.USE_DEFAULT_STREAM_TYPE; return AudioManager.USE_DEFAULT_STREAM_TYPE;
} }
} }
private StatusAnnouncerFactory() {}
} }
@@ -83,7 +83,7 @@ public class StatusAnnouncerTask implements PeriodicTask {
/** /**
* Called when the TTS engine is initialized. * 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); Log.i(MyTracksConstants.TAG, "TrackRecordingService.TTS init: " + status);
this.ready = (status == TextToSpeech.SUCCESS); this.ready = (status == TextToSpeech.SUCCESS);
@@ -635,17 +635,10 @@ public class TrackRecordingService extends Service implements LocationListener {
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
splitManager = new SplitManager(this); splitManager = new SplitManager(this);
PeriodicTask signalStrengthTask; SignalStrengthTaskFactory strengthTaskFactory =
if (ApiFeatures.hasModernSignalStrength()) { new SignalStrengthTaskFactory(ApiFeatures.getInstance());
Log.d(MyTracksConstants.TAG, signalManager =
"TrackRecordingService using modern signal strength api."); new TaskExecuterManager(-1, strengthTaskFactory.create(this), this);
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);
prefManager = new PreferenceManager(this); prefManager = new PreferenceManager(this);
prefManager.onSharedPreferenceChanged(null); prefManager.onSharedPreferenceChanged(null);
@@ -666,7 +659,7 @@ public class TrackRecordingService extends Service implements LocationListener {
} }
showNotification(); showNotification();
} }
/** /**
* Creates an {@link Executer} and schedules {@class SafeStatusAnnouncerTask}. * Creates an {@link Executer} and schedules {@class SafeStatusAnnouncerTask}.
* The announcer requires a TTS service and user should have enabled * 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() { private void setUpAnnouncer() {
if (announcementFrequency != -1) { if (announcementFrequency != -1) {
if (announcementExecuter == null) { if (announcementExecuter == null) {
PeriodicTask announcer = StatusAnnouncerFactory.create(this); StatusAnnouncerFactory statusAnnouncerFactory =
new StatusAnnouncerFactory(ApiFeatures.getInstance());
PeriodicTask announcer = statusAnnouncerFactory.create(this);
if (announcer == null) return; if (announcer == null) return;
announcementExecuter = new PeriodicTaskExecuter(announcer, this); announcementExecuter = new PeriodicTaskExecuter(announcer, this);
@@ -30,18 +30,42 @@ public class ApiFeatures {
*/ */
public static final int ANDROID_API_LEVEL = Integer.parseInt(Build.VERSION.SDK); 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. * Returns whether cloud backup (a.k.a. Froyo backup) is available.
*/ */
public static boolean hasBackup() { public boolean hasBackup() {
return ANDROID_API_LEVEL >= 8; return getApiLevel() >= 8;
} }
/** /**
* Returns whether text-to-speech is available. * Returns whether text-to-speech is available.
*/ */
public static boolean hasTextToSpeech() { public boolean hasTextToSpeech() {
if (ANDROID_API_LEVEL < 4) return false; if (getApiLevel() < 4) return false;
try { try {
Class.forName("android.speech.tts.TextToSpeech"); Class.forName("android.speech.tts.TextToSpeech");
@@ -54,9 +78,12 @@ public class ApiFeatures {
return true; return true;
} }
public static boolean hasModernSignalStrength() { public boolean hasModernSignalStrength() {
return ANDROID_API_LEVEL >= 7; 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());
}
}