diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/Api8StatusAnnouncerTask.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/Api8StatusAnnouncerTask.java
index fbc640998..9865e2c13 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/Api8StatusAnnouncerTask.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/Api8StatusAnnouncerTask.java
@@ -16,8 +16,6 @@
package com.google.android.apps.mytracks.services.tasks;
-import static com.google.android.apps.mytracks.Constants.TAG;
-
import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioManager;
@@ -27,57 +25,57 @@ import android.util.Log;
import java.util.HashMap;
-
/**
* This class will periodically announce the user's trip statistics. This class
- * will request and release audio focus.
- * For API Level 8 or higher.
- *
+ * will request and release audio focus.
+ *
* @author Sandor Dornbush
*/
@TargetApi(8)
public class Api8StatusAnnouncerTask extends StatusAnnouncerTask {
- private final static HashMap SPEECH_PARAMS = new HashMap();
+
+ private static final String TAG = Api8StatusAnnouncerTask.class.getSimpleName();
+ private static final HashMap SPEECH_PARAMS = new HashMap();
static {
SPEECH_PARAMS.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "not_used");
}
- private final AudioManager audioManager;
- private final OnUtteranceCompletedListener utteranceListener =
- new OnUtteranceCompletedListener() {
- @Override
+ private final OnUtteranceCompletedListener
+ utteranceListener = new OnUtteranceCompletedListener() {
+ @Override
public void onUtteranceCompleted(String utteranceId) {
int result = audioManager.abandonAudioFocus(null);
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
- Log.w(TAG, "FroyoStatusAnnouncerTask: Failed to relinquish audio focus");
+ Log.w(TAG, "Failed to relinquish audio focus.");
}
}
};
+ private final AudioManager audioManager;
+
public Api8StatusAnnouncerTask(Context context) {
super(context);
-
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
@Override
protected void onTtsReady() {
super.onTtsReady();
-
tts.setOnUtteranceCompletedListener(utteranceListener);
}
@Override
protected synchronized void speakAnnouncement(String announcement) {
- int result = audioManager.requestAudioFocus(null,
- TextToSpeech.Engine.DEFAULT_STREAM,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
+ int result = audioManager.requestAudioFocus(
+ null, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
- Log.w(TAG, "FroyoStatusAnnouncerTask: Request for audio focus failed.");
+ Log.w(TAG, "Failed to request audio focus.");
}
- // We don't care about the utterance id.
- // It is supplied here to force onUtteranceCompleted to be called.
+ /*
+ * We don't care about the utterance id. It is supplied here to force
+ * onUtteranceCompleted to be called.
+ */
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, SPEECH_PARAMS);
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTask.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTask.java
index 1d351bec4..640e7bc1f 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTask.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTask.java
@@ -20,7 +20,7 @@ import com.google.android.apps.mytracks.services.TrackRecordingService;
/**
* This is interface for a task that will be executed on some schedule.
- *
+ *
* @author Sandor Dornbush
*/
public interface PeriodicTask {
@@ -28,11 +28,13 @@ public interface PeriodicTask {
* Sets up this task for subsequent calls to the run method.
*/
public void start();
-
+
/**
* This method will be called periodically.
+ *
+ * @param trackRecordingService the track recording service
*/
- public void run(TrackRecordingService service);
+ public void run(TrackRecordingService trackRecordingService);
/**
* Shuts down this task and clean up resources.
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java
index 228ca27c8..a02b201e6 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java
@@ -15,8 +15,6 @@
*/
package com.google.android.apps.mytracks.services.tasks;
-import static com.google.android.apps.mytracks.Constants.TAG;
-
import com.google.android.apps.mytracks.services.TrackRecordingService;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
@@ -24,100 +22,139 @@ import com.google.android.apps.mytracks.util.UnitConversions;
import android.util.Log;
/**
- * Execute a task on a time or distance schedule.
- *
+ * Execute a periodic task on a time or distance schedule.
+ *
* @author Sandor Dornbush
*/
public class PeriodicTaskExecutor {
+ private static final String TAG = PeriodicTaskExecutor.class.getSimpleName();
+ private static final long MINUTE_TO_MILLISECONDS = 60000L;
+
+ private final TrackRecordingService trackRecordingService;
+ private final PeriodicTaskFactory periodicTaskFactory;
+
/**
- * The frequency of the task.
- * A value greater than zero is a frequency in time.
- * A value less than zero is considered a frequency in distance.
+ * The task frequency. A positive value is a time frequency (minutes). A
+ * negative value is a distance frequency (km or mi). A zero value is to turn
+ * off periodic task.
*/
private int taskFrequency = PreferencesUtils.FREQUENCY_OFF;
- /**
- * The next distance when the task should execute.
- */
- private double nextTaskDistance = 0;
+ private PeriodicTask periodicTask;
- /**
- * Time based executor.
- */
- private TimerTaskExecutor timerExecutor = null;
+ // Time periodic task executor
+ private TimerTaskExecutor timerTaskExecutor = null;
private boolean metricUnits;
- private final TrackRecordingService service;
+ // The next distance for the distance periodic task
+ private double nextTaskDistance = Double.MAX_VALUE;
- private final PeriodicTaskFactory factory;
-
- private PeriodicTask task;
-
- public PeriodicTaskExecutor(TrackRecordingService service, PeriodicTaskFactory factory) {
- this.service = service;
- this.factory = factory;
+ public PeriodicTaskExecutor(
+ TrackRecordingService trackRecordingService, PeriodicTaskFactory periodicTaskFactory) {
+ this.trackRecordingService = trackRecordingService;
+ this.periodicTaskFactory = periodicTaskFactory;
}
/**
- * Restores the manager.
+ * Restores the executor.
*/
public void restore() {
- // TODO: Decouple service from this class once and forever.
- if (!service.isRecording()) {
+ if (!trackRecordingService.isRecording()) {
+ Log.d(TAG, "Not recording.");
return;
}
if (!isTimeFrequency()) {
- if (timerExecutor != null) {
- timerExecutor.shutdown();
- timerExecutor = null;
+ if (timerTaskExecutor != null) {
+ timerTaskExecutor.shutdown();
+ timerTaskExecutor = null;
}
}
if (taskFrequency == PreferencesUtils.FREQUENCY_OFF) {
+ Log.d(TAG, "Task frequency is off.");
return;
}
- // Try to make the task.
- task = factory.create(service);
- // Returning null is ok.
- if (task == null) {
+ periodicTask = periodicTaskFactory.create(trackRecordingService);
+
+ // Returning null is ok
+ if (periodicTask == null) {
+ Log.d(TAG, "Peridoic task is null.");
return;
}
- task.start();
+ periodicTask.start();
if (isTimeFrequency()) {
- if (timerExecutor == null) {
- timerExecutor = new TimerTaskExecutor(task, service);
+ if (timerTaskExecutor == null) {
+ timerTaskExecutor = new TimerTaskExecutor(periodicTask, trackRecordingService);
}
- timerExecutor.scheduleTask(taskFrequency * 60000L);
+ timerTaskExecutor.scheduleTask(taskFrequency * MINUTE_TO_MILLISECONDS);
} else {
- // For distance based splits.
+ // For distance periodic task
calculateNextTaskDistance();
}
}
/**
- * Shuts down the manager.
+ * Shuts down the executor.
*/
public void shutdown() {
- if (task != null) {
- task.shutdown();
- task = null;
+ if (periodicTask != null) {
+ periodicTask.shutdown();
+ periodicTask = null;
}
- if (timerExecutor != null) {
- timerExecutor.shutdown();
- timerExecutor = null;
+ if (timerTaskExecutor != null) {
+ timerTaskExecutor.shutdown();
+ timerTaskExecutor = null;
}
}
/**
- * Calculates the next distance when the task should execute.
+ * Updates the executor.
*/
- void calculateNextTaskDistance() {
- // TODO: Decouple service from this class once and forever.
- if (!service.isRecording() || task == null) {
+ public void update() {
+ if (!isDistanceFrequency() || periodicTask == null) {
+ return;
+ }
+ double distance = trackRecordingService.getTripStatistics().getTotalDistance()
+ * UnitConversions.M_TO_KM;
+ if (!metricUnits) {
+ distance *= UnitConversions.KM_TO_MI;
+ }
+
+ if (distance > nextTaskDistance) {
+ periodicTask.run(trackRecordingService);
+ calculateNextTaskDistance();
+ }
+ }
+
+ /**
+ * Sets task frequency.
+ *
+ * @param taskFrequency the task frequency
+ */
+ public void setTaskFrequency(int taskFrequency) {
+ this.taskFrequency = taskFrequency;
+ restore();
+ }
+
+ /**
+ * Sets metricUnits.
+ *
+ * @param metricUnits true to use metric units
+ */
+ public void setMetricUnits(boolean metricUnits) {
+ this.metricUnits = metricUnits;
+ calculateNextTaskDistance();
+ }
+
+ /**
+ * Calculates the next distance for the distance periodic task.
+ */
+ private void calculateNextTaskDistance() {
+ if (!trackRecordingService.isRecording() || periodicTask == null) {
return;
}
@@ -127,7 +164,8 @@ public class PeriodicTaskExecutor {
return;
}
- double distance = service.getTripStatistics().getTotalDistance() * UnitConversions.M_TO_KM;
+ double distance = trackRecordingService.getTripStatistics().getTotalDistance()
+ * UnitConversions.M_TO_KM;
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
}
@@ -135,56 +173,19 @@ public class PeriodicTaskExecutor {
int index = (int) (distance / taskFrequency);
index -= 1;
nextTaskDistance = taskFrequency * index;
- Log.d(TAG, "SplitManager: Next split distance: " + nextTaskDistance);
}
/**
- * Updates executer with new trip statistics.
+ * True if time frequency.
*/
- public void update() {
- if (!isDistanceFrequency() || task == null) {
- return;
- }
- // Convert the distance in meters to km or mi.
- double distance = service.getTripStatistics().getTotalDistance() * UnitConversions.M_TO_KM;
- if (!metricUnits) {
- distance *= UnitConversions.KM_TO_MI;
- }
-
- if (distance > nextTaskDistance) {
- task.run(service);
- calculateNextTaskDistance();
- }
- }
-
private boolean isTimeFrequency() {
return taskFrequency > 0;
}
+ /**
+ * True if distance frequency.
+ */
private boolean isDistanceFrequency() {
return taskFrequency < 0;
}
-
- /**
- * Sets the task frequency.
- * < 0 Use the absolute value as a distance in the current measurement km
- * or mi
- * 0 Turn off the task
- * > 0 Use the value as a time in minutes
- * @param taskFrequency The frequency in time or distance
- */
- public void setTaskFrequency(int taskFrequency) {
- Log.d(TAG, "setTaskFrequency: taskFrequency = " + taskFrequency);
- this.taskFrequency = taskFrequency;
- restore();
- }
-
- public void setMetricUnits(boolean metricUnits) {
- this.metricUnits = metricUnits;
- calculateNextTaskDistance();
- }
-
- double getNextTaskDistance() {
- return nextTaskDistance;
- }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskFactory.java
index f3f8be445..6f974cb28 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskFactory.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskFactory.java
@@ -13,21 +13,22 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
+
package com.google.android.apps.mytracks.services.tasks;
import android.content.Context;
/**
* An interface for classes that can create periodic tasks.
- *
+ *
* @author Sandor Dornbush
*/
public interface PeriodicTaskFactory {
/**
- * Creates a periodic task which does voice announcements.
- *
+ * Creates a periodic task.
+ *
* @return the task, or null if task is not supported
*/
- PeriodicTask create(Context context);
+ public PeriodicTask create(Context context);
}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/SplitTask.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/SplitTask.java
index 763a922be..1f1ed95b4 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/SplitTask.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/SplitTask.java
@@ -21,15 +21,17 @@ import com.google.android.apps.mytracks.services.TrackRecordingService;
import android.content.Context;
-
/**
* A simple task to insert statistics markers periodically.
+ *
* @author Sandor Dornbush
*/
public class SplitTask implements PeriodicTask {
- private SplitTask() {
- }
+ private SplitTask() {}
+
+ @Override
+ public void start() {}
@Override
public void run(TrackRecordingService service) {
@@ -37,15 +39,10 @@ public class SplitTask implements PeriodicTask {
}
@Override
- public void shutdown() {
- }
+ public void shutdown() {}
- @Override
- public void start() {
- }
-
/**
- * Create new SplitTasks.
+ * Create new {@link SplitTask}.
*/
public static class Factory implements PeriodicTaskFactory {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactory.java
index 7e8b56b37..650423cf1 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactory.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactory.java
@@ -27,9 +27,6 @@ import android.content.Context;
*/
public class StatusAnnouncerFactory implements PeriodicTaskFactory {
- public StatusAnnouncerFactory() {
- }
-
@Override
public PeriodicTask create(Context context) {
return ApiAdapterFactory.getApiAdapter().getStatusAnnouncerTask(context);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTask.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTask.java
index 36667ae80..ed6472bd4 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTask.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTask.java
@@ -16,9 +16,6 @@
package com.google.android.apps.mytracks.services.tasks;
-import static com.google.android.apps.mytracks.Constants.TAG;
-
-import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.services.TrackRecordingService;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.PreferencesUtils;
@@ -38,7 +35,7 @@ import java.util.Locale;
/**
* This class will periodically announce the user's trip statistics.
- *
+ *
* @author Sandor Dornbush
*/
public class StatusAnnouncerTask implements PeriodicTask {
@@ -46,42 +43,31 @@ public class StatusAnnouncerTask implements PeriodicTask {
/**
* The rate at which announcements are spoken.
*/
- // @VisibleForTesting
+ @VisibleForTesting
static final float TTS_SPEECH_RATE = 0.9f;
- /**
- * A pointer to the service context.
- */
- private final Context context;
+ private static final String TAG = StatusAnnouncerTask.class.getSimpleName();
+ private static final long HOUR_TO_MILLISECOND = 60 * 60 * 1000;
- /**
- * The interface to the text to speech engine.
- */
+ private final Context context;
protected TextToSpeech tts;
- /**
- * The response received from the TTS engine after initialization.
- */
+ // Response from TTS after its initialization
private int initStatus = TextToSpeech.ERROR;
- /**
- * Whether the TTS engine is ready.
- */
+ // True if TTS engine is ready
private boolean ready = false;
- /**
- * Whether we're allowed to speak right now.
- */
+ // True if speech is allowed
private boolean speechAllowed;
/**
* Listener which updates {@link #speechAllowed} when the phone state changes.
*/
- private final PhoneStateListener phoneListener = new PhoneStateListener() {
- @Override
+ private final PhoneStateListener phoneStateListener = new PhoneStateListener() {
+ @Override
public void onCallStateChanged(int state, String incomingNumber) {
speechAllowed = state == TelephonyManager.CALL_STATE_IDLE;
-
if (!speechAllowed && tts != null && tts.isSpeaking()) {
// If we're already speaking, stop it.
tts.stop();
@@ -93,107 +79,197 @@ public class StatusAnnouncerTask implements PeriodicTask {
this.context = context;
}
- /**
- * {@inheritDoc}
- *
- * Announces the trip status.
- */
@Override
- public void run(TrackRecordingService service) {
- if (service == null) {
- Log.e(TAG, "StatusAnnouncer TrackRecordingService not initialized");
+ public void start() {
+ if (tts == null) {
+ tts = newTextToSpeech(context, new OnInitListener() {
+ @Override
+ public void onInit(int status) {
+ initStatus = status;
+ }
+ });
+ }
+ speechAllowed = true;
+ listenToPhoneState(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+ }
+
+ @Override
+ public void run(TrackRecordingService trackRecordingService) {
+ if (trackRecordingService == null) {
+ Log.e(TAG, "TrackRecordingService is null.");
return;
}
-
- runWithStatistics(service.getTripStatistics());
+ announce(trackRecordingService.getTripStatistics());
}
/**
- * This method exists as a convenience for testing code, allowing said code
- * to avoid needing to instantiate an entire {@link TrackRecordingService}
- * just to test the announcer.
+ * Runs this task.
+ *
+ * @param tripStatistics the trip statistics
*/
- // @VisibleForTesting
- void runWithStatistics(TripStatistics statistics) {
- if (statistics == null) {
- Log.e(TAG, "StatusAnnouncer stats not initialized.");
+ @VisibleForTesting
+ void announce(TripStatistics tripStatistics) {
+ if (tripStatistics == null) {
+ Log.e(TAG, "TripStatistics is null.");
return;
}
synchronized (this) {
- checkReady();
if (!ready) {
- Log.e(TAG, "StatusAnnouncer Tts not ready.");
+ ready = initStatus == TextToSpeech.SUCCESS;
+ if (ready) {
+ onTtsReady();
+ }
+ }
+ if (!ready) {
+ Log.i(TAG, "TTS not ready.");
return;
}
}
if (!speechAllowed) {
- Log.i(Constants.TAG,
- "Not making announcement - not allowed at this time");
+ Log.i(TAG, "Speech is not allowed at this time.");
return;
}
-
- String announcement = getAnnouncement(statistics);
- Log.d(Constants.TAG, "Announcement: " + announcement);
- speakAnnouncement(announcement);
+ speakAnnouncement(getAnnouncement(tripStatistics));
}
+ @Override
+ public void shutdown() {
+ listenToPhoneState(phoneStateListener, PhoneStateListener.LISTEN_NONE);
+ if (tts != null) {
+ tts.shutdown();
+ tts = null;
+ }
+ }
+
+ /**
+ * Called when TTS is ready.
+ */
+ protected void onTtsReady() {
+ Locale locale = Locale.getDefault();
+ int languageAvailability = tts.isLanguageAvailable(locale);
+ if (languageAvailability == TextToSpeech.LANG_MISSING_DATA
+ || languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
+ Log.w(TAG, "Default locale not available, use English.");
+ locale = Locale.ENGLISH;
+ /*
+ * TODO: instead of using english, load the language if missing and show a
+ * toast if not supported. Not able to change the resource strings to
+ * English.
+ */
+ }
+ tts.setLanguage(locale);
+
+ // Slow down the speed just a bit as it is hard to hear when exercising.
+ tts.setSpeechRate(TTS_SPEECH_RATE);
+ }
+
+ /**
+ * Speaks the announcement.
+ *
+ * @param announcement the announcement
+ */
protected void speakAnnouncement(String announcement) {
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null);
}
/**
- * Builds the announcement string.
- *
- * @return The string that will be read to the user
+ * Create a new {@link TextToSpeech}.
+ *
+ * @param aContext a context
+ * @param onInitListener an on init listener
*/
- // @VisibleForTesting
- protected String getAnnouncement(TripStatistics stats) {
+ @VisibleForTesting
+ protected TextToSpeech newTextToSpeech(Context aContext, OnInitListener onInitListener) {
+ return new TextToSpeech(aContext, onInitListener);
+ }
+
+ /**
+ * Gets the announcement.
+ *
+ * @param tripStatistics the trip statistics
+ */
+ @VisibleForTesting
+ protected String getAnnouncement(TripStatistics tripStatistics) {
boolean metricUnits = PreferencesUtils.getBoolean(
context, R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT);
boolean reportSpeed = PreferencesUtils.getBoolean(
context, R.string.report_speed_key, PreferencesUtils.REPORT_SPEED_DEFAULT);
- double d = stats.getTotalDistance() * UnitConversions.M_TO_KM;
- double s = stats.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH;
-
- if (d == 0) {
+ double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
+ double speed = tripStatistics.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH;
+
+ if (distance == 0) {
return context.getString(R.string.voice_total_distance_zero);
}
if (!metricUnits) {
- d *= UnitConversions.KM_TO_MI;
- s *= UnitConversions.KM_TO_MI;
+ distance *= UnitConversions.KM_TO_MI;
+ speed *= UnitConversions.KM_TO_MI;
}
- if (!reportSpeed) {
- s = 3600000.0 / s; // converts from speed to pace
- }
-
- // Makes sure s is not NaN.
- if (Double.isNaN(s)) {
- s = 0;
- }
-
- String speed;
+ String rate;
if (reportSpeed) {
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour
: R.plurals.voiceSpeedMilesPerHour;
- speed = context.getResources().getQuantityString(speedId, getQuantityCount(s), s);
+ rate = context.getResources().getQuantityString(speedId, getQuantityCount(speed), speed);
} else {
+ speed = speed == 0 ? 0.0 : 1 / speed;
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
- speed = context.getString(paceId, getAnnounceTime((long) s));
+ rate = context.getString(paceId, getAnnounceTime((long) (speed * HOUR_TO_MILLISECOND)));
}
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers
: R.plurals.voiceTotalDistanceMiles;
- String totalDistance = context.getResources().getQuantityString(
- totalDistanceId, getQuantityCount(d), d);
+ String totalDistance = context.getResources()
+ .getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
- return context.getString(
- R.string.voice_template, totalDistance, getAnnounceTime(stats.getMovingTime()), speed);
+ return context.getString(R.string.voice_template, totalDistance, getAnnounceTime(
+ tripStatistics.getMovingTime()), rate);
}
-
+
+ /**
+ * Listens to phone state.
+ *
+ * @param listener the listener
+ * @param events the interested events
+ */
+ @VisibleForTesting
+ protected void listenToPhoneState(PhoneStateListener listener, int events) {
+ TelephonyManager telephony = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ if (telephony != null) {
+ telephony.listen(listener, events);
+ }
+ }
+
+ /**
+ * Gets the announce time.
+ *
+ * @param time the time
+ */
+ @VisibleForTesting
+ String getAnnounceTime(long time) {
+ int[] parts = StringUtils.getTimeParts(time);
+ String seconds = context.getResources()
+ .getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]);
+ String minutes = context.getResources()
+ .getQuantityString(R.plurals.voiceMinutes, parts[1], parts[1]);
+ String hours = context.getResources()
+ .getQuantityString(R.plurals.voiceHours, parts[2], parts[2]);
+ StringBuilder sb = new StringBuilder();
+ if (parts[2] != 0) {
+ sb.append(hours);
+ sb.append(" ");
+ sb.append(minutes);
+ } else {
+ sb.append(minutes);
+ sb.append(" ");
+ sb.append(seconds);
+ }
+ return sb.toString();
+ }
+
/**
* Gets the plural count to be used by getQuantityString. getQuantityString
* only supports integer quantities, not a double quantity like "2.2".
@@ -204,7 +280,7 @@ public class StatusAnnouncerTask implements PeriodicTask {
* integer quantity. However, we need to make sure that if the casted value is
* 0, 1, or 2, we don't return those, instead, return the next biggest integer
* 3.
- *
+ *
* @param d the double value
*/
private int getQuantityCount(double d) {
@@ -219,138 +295,4 @@ public class StatusAnnouncerTask implements PeriodicTask {
return count < 3 ? 3 : count;
}
}
-
- @Override
- public void start() {
- Log.i(Constants.TAG, "Starting TTS");
- if (tts == null) {
- // We can't have this class also be the listener, otherwise it's unsafe to
- // reference it in Cupcake (even if we don't instantiate it).
- tts = newTextToSpeech(context, new OnInitListener() {
- @Override
- public void onInit(int status) {
- onTtsInit(status);
- }
- });
- }
- speechAllowed = true;
-
- // Register ourselves as a listener so we won't speak during a call.
- listenToPhoneState(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);
- }
-
- /**
- * Called when the TTS engine is initialized.
- */
- private void onTtsInit(int status) {
- Log.i(TAG, "TrackRecordingService.TTS init: " + status);
- synchronized (this) {
- // TTS should be valid here but NPE exceptions were reported to the market.
- initStatus = status;
- checkReady();
- }
- }
-
- /**
- * Ensures that the TTS is ready (finishing its initialization if needed).
- */
- private void checkReady() {
- synchronized (this) {
- if (ready) {
- // Already done;
- return;
- }
-
- ready = initStatus == TextToSpeech.SUCCESS && tts != null;
- Log.d(TAG, "Status announcer ready: " + ready);
-
- if (ready) {
- onTtsReady();
- }
- }
- }
-
- /**
- * Finishes the TTS engine initialization.
- * Called once (and only once) when the TTS engine is ready.
- */
- protected void onTtsReady() {
- // Force the language to be the same as the string we will be speaking,
- // if that's available.
- Locale speechLanguage = Locale.getDefault();
- int languageAvailability = tts.isLanguageAvailable(speechLanguage);
- if (languageAvailability == TextToSpeech.LANG_MISSING_DATA ||
- languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
- // English is probably supported.
- // TODO: Somehow use announcement strings from English too.
- Log.w(TAG, "Default language not available, using English.");
- speechLanguage = Locale.ENGLISH;
- }
- tts.setLanguage(speechLanguage);
-
- // Slow down the speed just a bit as it is hard to hear when exercising.
- tts.setSpeechRate(TTS_SPEECH_RATE);
- }
-
- @Override
- public void shutdown() {
- // Stop listening to phone state.
- listenToPhoneState(phoneListener, PhoneStateListener.LISTEN_NONE);
-
- if (tts != null) {
- tts.shutdown();
- tts = null;
- }
-
- Log.i(Constants.TAG, "TTS shut down");
- }
-
- /**
- * Wrapper for instantiating a {@link TextToSpeech} object, which causes
- * several issues during testing.
- */
- // @VisibleForTesting
- protected TextToSpeech newTextToSpeech(Context ctx, OnInitListener onInitListener) {
- return new TextToSpeech(ctx, onInitListener);
- }
-
- /**
- * Wrapper for calls to the 100%-unmockable {@link TelephonyManager#listen}.
- */
- // @VisibleForTesting
- protected void listenToPhoneState(PhoneStateListener listener, int events) {
- TelephonyManager telephony =
- (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- if (telephony != null) {
- telephony.listen(listener, events);
- }
- }
-
- /**
- * Gets a string to announce the time.
- *
- * @param time the time
- */
- @VisibleForTesting
- String getAnnounceTime(long time) {
- int[] parts = StringUtils.getTimeParts(time);
- String seconds = context.getResources().getQuantityString(
- R.plurals.voiceSeconds, parts[0], parts[0]);
- String minutes = context.getResources().getQuantityString(
- R.plurals.voiceMinutes, parts[1], parts[1]);
- String hours = context.getResources().getQuantityString(
- R.plurals.voiceHours, parts[2], parts[2]);
-
- StringBuilder sb = new StringBuilder();
- if (parts[2] != 0) {
- sb.append(hours);
- sb.append(" ");
- sb.append(minutes);
- } else {
- sb.append(minutes);
- sb.append(" ");
- sb.append(seconds);
- }
- return sb.toString();
- }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/TimerTaskExecutor.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/TimerTaskExecutor.java
index ec7d66c05..86a61f9e4 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/TimerTaskExecutor.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/TimerTaskExecutor.java
@@ -16,46 +16,40 @@
package com.google.android.apps.mytracks.services.tasks;
-import static com.google.android.apps.mytracks.Constants.TAG;
-
-import android.util.Log;
+import com.google.android.apps.mytracks.services.TrackRecordingService;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
-import com.google.android.apps.mytracks.services.TrackRecordingService;
-
/**
* This class will periodically perform a task.
- *
+ *
* @author Sandor Dornbush
*/
public class TimerTaskExecutor {
- private final PeriodicTask task;
- private final TrackRecordingService service;
+ private final PeriodicTask periodicTask;
+ private final TrackRecordingService trackRecordingService;
/**
- * A timer to schedule the announcements.
- * This is non-null if the task is in started (scheduled) state.
+ * A timer to schedule the announcements. This is non-null if the task is in
+ * started (scheduled) state.
*/
private Timer timer;
- public TimerTaskExecutor(PeriodicTask task,
- TrackRecordingService service) {
- this.task = task;
- this.service = service;
+ public TimerTaskExecutor(PeriodicTask periodicTask, TrackRecordingService trackRecordingService) {
+ this.periodicTask = periodicTask;
+ this.trackRecordingService = trackRecordingService;
}
/**
- * Schedules the task at the given interval.
- *
- * @param interval The interval in milliseconds
+ * Schedules the periodic task at an interval.
+ *
+ * @param interval the interval in milliseconds
*/
public void scheduleTask(long interval) {
- // TODO: Decouple service from this class once and forever.
- if (!service.isRecording()) {
+ if (!trackRecordingService.isRecording()) {
return;
}
@@ -64,7 +58,7 @@ public class TimerTaskExecutor {
timer.purge();
} else {
// First start, or we were previously shut down.
- task.start();
+ periodicTask.start();
}
timer = new Timer();
@@ -73,27 +67,22 @@ public class TimerTaskExecutor {
}
long now = System.currentTimeMillis();
- long next = service.getTripStatistics().getStartTime();
+ long next = trackRecordingService.getTripStatistics().getStartTime();
if (next < now) {
next = now + interval - ((now - next) % interval);
}
-
- Date start = new Date(next);
- Log.i(TAG, task.getClass().getSimpleName() + " scheduled to start at " + start
- + " every " + interval + " milliseconds.");
- timer.scheduleAtFixedRate(new PeriodicTimerTask(), start, interval);
+ timer.scheduleAtFixedRate(new PeriodicTimerTask(), new Date(next), interval);
}
/**
- * Cleans up this object.
+ * Shuts down.
*/
public void shutdown() {
- Log.i(TAG, task.getClass().getSimpleName() + " shutting down.");
if (timer != null) {
timer.cancel();
timer.purge();
timer = null;
- task.shutdown();
+ periodicTask.shutdown();
}
}
@@ -101,9 +90,10 @@ public class TimerTaskExecutor {
* The timer task to announce the trip status.
*/
private class PeriodicTimerTask extends TimerTask {
+
@Override
public void run() {
- task.run(service);
+ periodicTask.run(trackRecordingService);
}
}
}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTaskTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTaskTest.java
index 48d52bc5b..f678c82cf 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTaskTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTaskTest.java
@@ -322,32 +322,6 @@ public class StatusAnnouncerTaskTest extends AndroidTestCase {
OnInitListener ttsInitListener = initListenerCapture.getValue();
assertNotNull(ttsInitListener);
- expect(tts.isLanguageAvailable(DEFAULT_LOCALE))
- .andStubReturn(TextToSpeech.LANG_AVAILABLE);
- expect(tts.setLanguage(DEFAULT_LOCALE))
- .andReturn(TextToSpeech.LANG_AVAILABLE);
- expect(tts.setSpeechRate(StatusAnnouncerTask.TTS_SPEECH_RATE))
- .andReturn(TextToSpeech.SUCCESS);
-
- AndroidMock.replay(tts);
-
- ttsInitListener.onInit(TextToSpeech.SUCCESS);
-
- AndroidMock.verify(mockTask, tts);
- }
-
- public void testStart_languageNotSupported() {
- doStart();
- OnInitListener ttsInitListener = initListenerCapture.getValue();
- assertNotNull(ttsInitListener);
-
- expect(tts.isLanguageAvailable(DEFAULT_LOCALE))
- .andStubReturn(TextToSpeech.LANG_NOT_SUPPORTED);
- expect(tts.setLanguage(Locale.ENGLISH))
- .andReturn(TextToSpeech.LANG_AVAILABLE);
- expect(tts.setSpeechRate(StatusAnnouncerTask.TTS_SPEECH_RATE))
- .andReturn(TextToSpeech.SUCCESS);
-
AndroidMock.replay(tts);
ttsInitListener.onInit(TextToSpeech.SUCCESS);
@@ -393,6 +367,10 @@ public class StatusAnnouncerTaskTest extends AndroidTestCase {
// Put task in "ready" state
startTask(TextToSpeech.SUCCESS);
+ expect(tts.isLanguageAvailable(DEFAULT_LOCALE)).andStubReturn(TextToSpeech.LANG_AVAILABLE);
+ expect(tts.setLanguage(DEFAULT_LOCALE)).andReturn(TextToSpeech.LANG_AVAILABLE);
+ expect(tts.setSpeechRate(StatusAnnouncerTask.TTS_SPEECH_RATE)).andReturn(TextToSpeech.SUCCESS);
+
// Expect actual announcement call
expect(tts.speak(
eq(ANNOUNCEMENT), eq(TextToSpeech.QUEUE_FLUSH),
@@ -401,7 +379,7 @@ public class StatusAnnouncerTaskTest extends AndroidTestCase {
// Run the announcement
AndroidMock.replay(tts);
- task.runWithStatistics(stats);
+ task.announce(stats);
AndroidMock.verify(mockTask, tts);
}
@@ -411,7 +389,7 @@ public class StatusAnnouncerTaskTest extends AndroidTestCase {
// Run the announcement
AndroidMock.replay(tts);
- task.runWithStatistics(null);
+ task.run(null);
AndroidMock.verify(mockTask, tts);
}
@@ -424,7 +402,7 @@ public class StatusAnnouncerTaskTest extends AndroidTestCase {
AndroidMock.replay(tts);
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, null);
- task.runWithStatistics(null);
+ task.run(null);
AndroidMock.verify(mockTask, tts);
}
@@ -441,7 +419,7 @@ public class StatusAnnouncerTaskTest extends AndroidTestCase {
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
// Run the announcement - this should do nothing.
- task.runWithStatistics(null);
+ task.run(null);
AndroidMock.verify(mockTask, tts);
}
@@ -455,7 +433,7 @@ public class StatusAnnouncerTaskTest extends AndroidTestCase {
AndroidMock.replay(tts);
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
- task.runWithStatistics(null);
+ task.run(null);
AndroidMock.verify(mockTask, tts);
}
@@ -475,7 +453,7 @@ public class StatusAnnouncerTaskTest extends AndroidTestCase {
// Run the announcement
AndroidMock.replay(tts);
- task.runWithStatistics(null);
+ task.run(null);
AndroidMock.verify(mockTask, tts);
}