Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix voiceLanguage NPE and add tests for NavigationSpeechPlayer #1054

Merged
merged 1 commit into from
Jun 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.location.Location;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.annotation.NonNull;
import android.text.TextUtils;

import com.mapbox.api.directions.v5.DirectionsCriteria;
Expand All @@ -22,7 +23,9 @@
import com.mapbox.services.android.navigation.ui.v5.route.ViewRouteFetcher;
import com.mapbox.services.android.navigation.ui.v5.route.ViewRouteListener;
import com.mapbox.services.android.navigation.ui.v5.summary.SummaryModel;
import com.mapbox.services.android.navigation.ui.v5.voice.NavigationInstructionPlayer;
import com.mapbox.services.android.navigation.ui.v5.voice.NavigationSpeechPlayer;
import com.mapbox.services.android.navigation.ui.v5.voice.SpeechAnnouncement;
import com.mapbox.services.android.navigation.ui.v5.voice.SpeechPlayerProvider;
import com.mapbox.services.android.navigation.v5.milestone.BannerInstructionMilestone;
import com.mapbox.services.android.navigation.v5.milestone.Milestone;
import com.mapbox.services.android.navigation.v5.milestone.MilestoneEventListener;
Expand Down Expand Up @@ -58,7 +61,7 @@ public class NavigationViewModel extends AndroidViewModel {
private ViewRouteFetcher navigationViewRouteEngine;
private LocationEngineConductor locationEngineConductor;
private NavigationViewEventDispatcher navigationViewEventDispatcher;
private NavigationInstructionPlayer instructionPlayer;
private NavigationSpeechPlayer instructionPlayer;
private ConnectivityManager connectivityManager;
private RouteProgress routeProgress;
private String feedbackId;
Expand Down Expand Up @@ -212,9 +215,19 @@ private void initUnitType(NavigationUiOptions options) {
unitType = options.directionsRoute().routeOptions().voiceUnits();
}

private void initTimeFormat(MapboxNavigationOptions options) {
timeFormatType = options.timeFormatType();
}

private void initVoiceInstructions(NavigationViewOptions options) {
boolean languageSupported = options.directionsRoute().voiceLanguage() != null;
instructionPlayer = new NavigationInstructionPlayer(getApplication(), language, languageSupported, accessToken);
boolean isVoiceLanguageSupported = options.directionsRoute().voiceLanguage() != null;
SpeechPlayerProvider speechPlayerProvider = initializeSpeechPlayerProvider(isVoiceLanguageSupported);
instructionPlayer = new NavigationSpeechPlayer(speechPlayerProvider);
}

@NonNull
private SpeechPlayerProvider initializeSpeechPlayerProvider(boolean voiceLanguageSupported) {
return new SpeechPlayerProvider(getApplication(), language, voiceLanguageSupported, accessToken);
}

private void initNavigation(Context context, MapboxNavigationOptions options) {
Expand All @@ -238,10 +251,6 @@ private void addMilestones(NavigationViewOptions options) {
}
}

private void initTimeFormat(MapboxNavigationOptions options) {
timeFormatType = options.timeFormatType();
}

private ProgressChangeListener progressChangeListener = new ProgressChangeListener() {
@Override
public void onProgressChange(Location location, RouteProgress routeProgress) {
Expand All @@ -267,7 +276,9 @@ public void userOffRoute(Location location) {
@Override
public void onMilestoneEvent(RouteProgress routeProgress, String instruction, Milestone milestone) {
if (milestone instanceof VoiceInstructionMilestone) {
instructionPlayer.play((VoiceInstructionMilestone) milestone);
SpeechAnnouncement speechAnnouncement = SpeechAnnouncement.builder()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if you pass in VoiceInstructionMilestone into a constructor in SpeechAnnouncement so this logic can be in SpeechAnnouncement? Instead of using the builder pattern

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was thinking here in terms of usability outside of the turn-by-turn UI. NavigationSpeechPlayer is a public API, so tying it completely to the VoiceInstructionMilestone in order to voice something may not make a lot of sense to someone that doesn't want to use the milestones to voice something.

I did update the builder though to also take a VoiceInstructionMilestone, does that work? Any reasons you don't like the builder pattern here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense! And yea having a builder method that takes in a VoiceInstructionMilestone for convenience is 👍

.voiceInstructionMilestone((VoiceInstructionMilestone) milestone).build();
instructionPlayer.play(speechAnnouncement);
}
updateBannerInstruction(routeProgress, milestone);
sendEventArrival(routeProgress, milestone);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,28 @@
*
* @since 0.6.0
*/
class AndroidSpeechPlayer implements InstructionPlayer {
class AndroidSpeechPlayer implements SpeechPlayer {

private static final String DEFAULT_UTTERANCE_ID = "default_id";

private TextToSpeech textToSpeech;
private SpeechListener speechListener;

private boolean isMuted;
private boolean languageSupported = false;
private InstructionListener instructionListener;

/**
* Creates an instance of {@link AndroidSpeechPlayer}.
*
* @param context used to create an instance of {@link TextToSpeech}
* @param context used to create an instance of {@link TextToSpeech}

This comment was marked as resolved.

* @param language to initialize locale to set
* @since 0.6.0
*/
AndroidSpeechPlayer(Context context, final String language, final InstructionListener instructionListener) {
AndroidSpeechPlayer(Context context, final String language, final SpeechListener speechListener) {
textToSpeech = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
setInstructionListener(instructionListener);
setSpeechListener(speechListener);

boolean ableToInitialize = status != TextToSpeech.ERROR && language != null;
if (!ableToInitialize) {
Expand All @@ -52,11 +53,13 @@ public void onInit(int status) {
/**
* Plays the given voice instruction using TTS
*
* @param instruction voice instruction to be synthesized and played
* @param speechAnnouncement with voice instruction to be synthesized and played
*/
@Override
public void play(String instruction) {
boolean canPlay = languageSupported && !isMuted && !TextUtils.isEmpty(instruction);
public void play(SpeechAnnouncement speechAnnouncement) {
boolean isValidAnnouncement = speechAnnouncement != null
&& !TextUtils.isEmpty(speechAnnouncement.announcement());
boolean canPlay = isValidAnnouncement && languageSupported && !isMuted;
if (!canPlay) {
return;
}
Expand All @@ -65,7 +68,7 @@ public void play(String instruction) {

HashMap<String, String> params = new HashMap<>(1);
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, DEFAULT_UTTERANCE_ID);
textToSpeech.speak(instruction, TextToSpeech.QUEUE_ADD, params);
textToSpeech.speak(speechAnnouncement.announcement(), TextToSpeech.QUEUE_ADD, params);
}

/**
Expand Down Expand Up @@ -129,17 +132,17 @@ private void initializeWithLanguage(Locale language) {

private void fireInstructionListenerIfApi14() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
instructionListener.onStart();
speechListener.onStart();
}
}

private void setInstructionListener(final InstructionListener instructionListener) {
this.instructionListener = instructionListener;
private void setSpeechListener(final SpeechListener speechListener) {
this.speechListener = speechListener;

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
textToSpeech.setOnUtteranceCompletedListener(new Api14UtteranceListener(instructionListener));
textToSpeech.setOnUtteranceCompletedListener(new Api14UtteranceListener(speechListener));
} else {
textToSpeech.setOnUtteranceProgressListener(new UtteranceListener(instructionListener));
textToSpeech.setOnUtteranceProgressListener(new UtteranceListener(speechListener));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import android.speech.tts.TextToSpeech;

class Api14UtteranceListener implements TextToSpeech.OnUtteranceCompletedListener {
private InstructionListener instructionListener;
private SpeechListener speechListener;

Api14UtteranceListener(InstructionListener instructionListener) {
this.instructionListener = instructionListener;
Api14UtteranceListener(SpeechListener speechListener) {
this.speechListener = speechListener;
}

@Override
public void onUtteranceCompleted(String utteranceId) {
instructionListener.onDone();
speechListener.onDone();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.mapbox.services.android.navigation.ui.v5.voice;

import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import android.support.annotation.RequiresApi;

@RequiresApi(api = Build.VERSION_CODES.O)
class Api26AudioFocusDelegate implements AudioFocusDelegate {

private static final int FOCUS_GAIN = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;

private final AudioManager audioManager;
private final AudioFocusRequest audioFocusRequest;

Api26AudioFocusDelegate(AudioManager audioManager) {
this.audioManager = audioManager;
audioFocusRequest = new AudioFocusRequest.Builder(FOCUS_GAIN).build();
}

@Override
public void requestFocus() {
audioManager.requestAudioFocus(audioFocusRequest);
}

@Override
public void abandonFocus() {
audioManager.abandonAudioFocusRequest(audioFocusRequest);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.mapbox.services.android.navigation.ui.v5.voice;

interface AudioFocusDelegate {

void requestFocus();

void abandonFocus();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.mapbox.services.android.navigation.ui.v5.voice;

import android.media.AudioManager;
import android.os.Build;

class AudioFocusDelegateProvider {

private final AudioFocusDelegate audioFocusDelegate;

AudioFocusDelegateProvider(AudioManager audioManager) {
audioFocusDelegate = buildAudioFocusDelegate(audioManager);
}

AudioFocusDelegate retrieveAudioFocusDelegate() {
return audioFocusDelegate;
}

private AudioFocusDelegate buildAudioFocusDelegate(AudioManager audioManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return new Api26AudioFocusDelegate(audioManager);
}
return new SpeechAudioFocusDelegate(audioManager);
}
}

This file was deleted.

Loading