Added full english JS tts support (needs performance optimization)
This commit is contained in:
parent
266aa817ec
commit
55232223a3
7 changed files with 515 additions and 51 deletions
|
@ -2933,4 +2933,5 @@
|
|||
<string name="how_to_open_wiki_title">How to open Wikipedia articles?</string>
|
||||
<string name="use_js_voice_guidance">Use JS voice guidance</string>
|
||||
<string name="use_js_voice_guidance_description">Use new voice guidance logic based on JavaScript</string>
|
||||
<string name="test_voice_desrc">Tap a button and listen to the corresponding voice prompt to identify missing or faulty propmts.</string>
|
||||
</resources>
|
||||
|
|
|
@ -579,7 +579,7 @@ public class AppInitializer implements IProgress {
|
|||
throw new CommandPlayerException(ctx.getString(R.string.voice_data_unavailable));
|
||||
}
|
||||
if (app.getSettings().USE_JS_VOICE_GUIDANCE.get()) {
|
||||
return new JSTTSCommandPlayerImpl(osmandApplication, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
|
||||
return new JSTTSCommandPlayerImpl(ctx, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
|
||||
}
|
||||
if (MediaCommandPlayerImpl.isMyData(voiceDir)) {
|
||||
return new MediaCommandPlayerImpl(osmandApplication, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
|
||||
|
|
|
@ -22,6 +22,8 @@ import net.osmand.plus.OsmandApplication;
|
|||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.activities.OsmandActionBarActivity;
|
||||
import net.osmand.plus.voice.AbstractPrologCommandPlayer;
|
||||
import net.osmand.plus.voice.JSCommandBuilder;
|
||||
import net.osmand.plus.voice.JSTTSCommandPlayerImpl;
|
||||
import net.osmand.plus.voice.TTSCommandPlayerImpl;
|
||||
import net.osmand.plus.voice.CommandBuilder;
|
||||
import net.osmand.plus.voice.CommandPlayer;
|
||||
|
@ -29,7 +31,9 @@ import net.osmand.plus.routing.VoiceRouter;
|
|||
import net.osmand.util.Algorithms;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import alice.tuprolog.Struct;
|
||||
|
@ -62,7 +66,7 @@ public class TestVoiceActivity extends OsmandActionBarActivity {
|
|||
gl.setPadding(3, 3, 3, 3);
|
||||
|
||||
TextView tv = new TextView(this);
|
||||
tv.setText("Tap a button and listen to the corresponding voice prompt to identify missing or faulty propmts.");
|
||||
tv.setText(R.string.test_voice_desrc);
|
||||
tv.setPadding(0, 5, 0, 7);
|
||||
|
||||
ScrollView sv = new ScrollView(this);
|
||||
|
@ -75,7 +79,7 @@ public class TestVoiceActivity extends OsmandActionBarActivity {
|
|||
|
||||
// add buttons
|
||||
setContentView(gl);
|
||||
getSupportActionBar(). setTitle(R.string.test_voice_prompts);
|
||||
getSupportActionBar().setTitle(R.string.test_voice_prompts);
|
||||
|
||||
selectVoice(ll);
|
||||
}
|
||||
|
@ -216,6 +220,10 @@ public class TestVoiceActivity extends OsmandActionBarActivity {
|
|||
}
|
||||
|
||||
private void addButtons(final LinearLayout ll, CommandPlayer p) {
|
||||
if (p instanceof JSTTSCommandPlayerImpl) {
|
||||
addJSTTSPrompts(ll, (JSTTSCommandPlayerImpl) p);
|
||||
return;
|
||||
}
|
||||
addButton(ll, "Route calculated and number tests:", builder(p));
|
||||
addButton(ll, "\u25BA (1.1) New route calculated, 150m, 230sec (00:03:50)", builder(p).newRouteCalculated(150, 230));
|
||||
addButton(ll, "\u25BA (1.2) New route calculated, 1350m, 3680sec (01:01:20)", builder(p).newRouteCalculated(1350, 3680));
|
||||
|
@ -292,10 +300,108 @@ public class TestVoiceActivity extends OsmandActionBarActivity {
|
|||
ll.forceLayout();
|
||||
}
|
||||
|
||||
private Map<String, String> jsStreet(CommandPlayer p, String... args) {
|
||||
Map<String, String> res = new HashMap<>();
|
||||
if (!p.supportsStructuredStreetNames()) {
|
||||
return res;
|
||||
}
|
||||
String[] streetNames = new String[]{"toRef", "toStreetName", "toDest", "fromRef", "fromStreetName", "fromDest"};
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
res.put(streetNames[i], args[i]);
|
||||
}
|
||||
for (String streetName : streetNames) {
|
||||
if (res.get(streetName) == null) {
|
||||
res.put(streetName, "");
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private void addJSTTSPrompts(LinearLayout ll, JSTTSCommandPlayerImpl p) {
|
||||
addButton(ll, "Route calculated and number tests:", jsBuilder(p));
|
||||
addButton(ll, "\u25BA (1.1) New route calculated, 150m, 230sec (00:03:50)", jsBuilder(p).newRouteCalculated(150, 230));
|
||||
addButton(ll, "\u25BA (1.2) New route calculated, 1350m, 3680sec (01:01:20)", jsBuilder(p).newRouteCalculated(1350, 3680));
|
||||
addButton(ll, "\u25BA (1.3) New route calculated 3700m, 7320sec (02:02)", jsBuilder(p).newRouteCalculated(3700, 7320));
|
||||
addButton(ll, "\u25BA (1.4) New route calculated 9100m, 10980sec (03:03)", jsBuilder(p).newRouteCalculated(9100, 10980));
|
||||
addButton(ll, "\u25BA (2.1) Route recalculated 11500m, 18600sec (05:10)", jsBuilder(p).routeRecalculated(11500, 18600));
|
||||
addButton(ll, "\u25BA (2.2) Route recalculated 19633m, 26700sec (07:25)", jsBuilder(p).routeRecalculated(19633, 26700));
|
||||
addButton(ll, "\u25BA (2.3) Route recalculated 89750m, 55800sec (15:30)", jsBuilder(p).routeRecalculated(89750, 55800));
|
||||
addButton(ll, "\u25BA (2.4) Route recalculated 125900m, 92700sec (25:45)", jsBuilder(p).routeRecalculated(125900, 92700));
|
||||
|
||||
addButton(ll, "All turn types: prepareTurn, makeTurnIn, turn:", jsBuilder(p));
|
||||
addButton(ll, "\u25BA (3.1) After 1520m turn slightly left", jsBuilder(p).prepareTurn(AbstractPrologCommandPlayer.A_LEFT_SL, 1520, jsStreet(p, "")));
|
||||
addButton(ll, "\u25BA (3.2) In 450m turn sharply left onto 'Hauptstra"+"\u00df"+"e', then bear right", jsBuilder(p).turn(AbstractPrologCommandPlayer.A_LEFT_SH, 450, jsStreet(p, "Hauptstraße")).then().bearRight(jsStreet(p, "")));
|
||||
addButton(ll, "\u25BA (3.3) Turn left, then in 100m turn slightly right", jsBuilder(p).turn(AbstractPrologCommandPlayer.A_LEFT, jsStreet(p, "")).then().turn(AbstractPrologCommandPlayer.A_RIGHT_SL, 100, jsStreet(p, "")));
|
||||
addButton(ll, "\u25BA (3.4) After 3100m turn right onto 'SR 80' toward 'Rome'", jsBuilder(p).prepareTurn(AbstractPrologCommandPlayer.A_RIGHT, 3100, jsStreet(p, "", "SR 80", "Rome")));
|
||||
addButton(ll, "\u25BA (3.5) In 370m turn slightly right onto 'Route 23' 'Main jsStreet', then bear left", jsBuilder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SL, 370, jsStreet(p, "Main jsStreet", "Route 23")).then().bearLeft(jsStreet(p, "")));
|
||||
addButton(ll, "\u25BA (3.6) Turn sharply right onto 'Dr.-Quinn-Stra"+"\u00df"+"e'", jsBuilder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SH, jsStreet(p, "Dr.-Quinn-Straße")));
|
||||
|
||||
addButton(ll, "Keep left/right: prepareTurn, makeTurnIn, turn:", jsBuilder(p));
|
||||
addButton(ll, "\u25BA (4.1) After 1810m keep left ' '", jsBuilder(p).prepareTurn(AbstractPrologCommandPlayer.A_LEFT_KEEP, 1810, jsStreet(p, "")));
|
||||
addButton(ll, "\u25BA (4.2) In 400m keep left ' ' then in 80m keep right onto 'A1'", jsBuilder(p).turn(AbstractPrologCommandPlayer.A_LEFT_KEEP, 400, jsStreet(p, "")).then().turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, 80, jsStreet(p,"", "A1")));
|
||||
addButton(ll, "\u25BA (4.3) Keep right on 'Highway 60'", jsBuilder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, jsStreet(p, "Highway 60", "", "", "Highway 60")));
|
||||
addButton(ll, "\u25BA (4.4) Turn left onto 'Broadway', then in 100m keep right and arrive at your destination 'Town Hall'",
|
||||
jsBuilder(p).turn(AbstractPrologCommandPlayer.A_LEFT, jsStreet(p, "Broadway"))
|
||||
.then().turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, 100, jsStreet(p, "")).andArriveAtDestination("Town Hall"));
|
||||
|
||||
addButton(ll, "Roundabouts: prepareTurn, makeTurnIn, turn:", jsBuilder(p));
|
||||
addButton(ll, "\u25BA (5.1) After 1250m enter a roundabout", jsBuilder(p).prepareRoundAbout(1250, 3, jsStreet(p,"", "I 15", "Los Angeles")));
|
||||
addButton(ll, "\u25BA (5.2) In 450m enter the roundabout and take the 1st exit onto 'I 15' toward 'Los Angeles'", jsBuilder(p).roundAbout(450, 0, 1, jsStreet(p,"", "I 15", "Los Angeles")));
|
||||
addButton(ll, "\u25BA (5.3) Roundabout: Take the 2nd exit onto 'Highway 60'", jsBuilder(p).roundAbout(0, 2, jsStreet(p, "Highway 60")));
|
||||
|
||||
addButton(ll, "U-turns: prepareTurn, makeTurnIn, turn, when possible:", jsBuilder(p));
|
||||
addButton(ll, "\u25BA (6.1) After 640m make a U-turn", jsBuilder(p).prepareMakeUT(640, jsStreet(p, "")));
|
||||
addButton(ll, "\u25BA (6.2) In 400m make a U-turn", jsBuilder(p).makeUT(400, jsStreet(p, "")));
|
||||
addButton(ll, "\u25BA (6.3) Make a U-turn on 'Riviera'", jsBuilder(p).makeUT(jsStreet(p, "Riviera", "", "", "Riviera")));
|
||||
addButton(ll, "\u25BA (6.4) When possible, make a U-turn", jsBuilder(p).makeUTwp());
|
||||
|
||||
addButton(ll, "Go straight, follow the road, approaching:", jsBuilder(p));
|
||||
addButton(ll, "\u25BA (7.1) Straight ahead", jsBuilder(p).goAhead());
|
||||
addButton(ll, "\u25BA (7.2) Continue for 2350m to ' '", jsBuilder(p).goAhead(2350, jsStreet(p, "")));
|
||||
addButton(ll, "\u25BA (7.3) Continue for 360m to 'Broadway' and arrive at your intermediate destination ' '", jsBuilder(p).goAhead(360, jsStreet(p,"Broadway")).andArriveAtIntermediatePoint(""));
|
||||
addButton(ll, "\u25BA (7.4) Continue for 800m to 'Dr Martin Luther King Jr Boulevard' and arrive at your destination ' '", jsBuilder(p).goAhead(800, jsStreet(p,"", "Dr Martin Luther King Jr Boulevard")).andArriveAtDestination(""));
|
||||
addButton(ll, "\u25BA (7.5) Continue for 200m and pass GPX waypoint 'Trailhead'", jsBuilder(p).goAhead(200, new HashMap<String, String>()).andArriveAtWayPoint("Trailhead"));
|
||||
addButton(ll, "\u25BA (7.6) Continue for 400m and pass favorite 'Brewery'", jsBuilder(p).goAhead(400, new HashMap<String, String>()).andArriveAtFavorite("Brewery"));
|
||||
addButton(ll, "\u25BA (7.7) Continue for 600m and pass POI 'Museum'", jsBuilder(p).goAhead(600, new HashMap<String, String>()).andArriveAtPoi("Museum"));
|
||||
|
||||
addButton(ll, "Arriving and passing points:", jsBuilder(p));
|
||||
addButton(ll, "\u25BA (8.1) Arrive at your destination 'Home'", jsBuilder(p).arrivedAtDestination("Home"));
|
||||
addButton(ll, "\u25BA (8.2) Arrive at your intermediate destination 'Friend'", jsBuilder(p).arrivedAtIntermediatePoint("Friend"));
|
||||
addButton(ll, "\u25BA (8.3) Passing GPX waypoint 'Trailhead'", jsBuilder(p).arrivedAtWayPoint("Trailhead"));
|
||||
addButton(ll, "\u25BA (8.4) Passing favorite 'Brewery'", jsBuilder(p).arrivedAtFavorite("Brewery"));
|
||||
addButton(ll, "\u25BA (8.5) Passing POI 'Museum'", jsBuilder(p).arrivedAtPoi("Museum"));
|
||||
|
||||
addButton(ll, "Attention prompts:", jsBuilder(p));
|
||||
addButton(ll, "\u25BA (9.1) You are exceeding the speed limit '50' (18 m/s)", jsBuilder(p).speedAlarm(50, 18f));
|
||||
addButton(ll, "\u25BA (9.2) Attention, speed camera", jsBuilder(p).attention("SPEED_CAMERA"));
|
||||
addButton(ll, "\u25BA (9.3) Attention, border control", jsBuilder(p).attention("BORDER_CONTROL"));
|
||||
addButton(ll, "\u25BA (9.4) Attention, railroad crossing", jsBuilder(p).attention("RAILWAY"));
|
||||
addButton(ll, "\u25BA (9.5) Attention, traffic calming", jsBuilder(p).attention("TRAFFIC_CALMING"));
|
||||
addButton(ll, "\u25BA (9.6) Attention, toll booth", jsBuilder(p).attention("TOLL_BOOTH"));
|
||||
addButton(ll, "\u25BA (9.7) Attention, stop sign", jsBuilder(p).attention("STOP"));
|
||||
addButton(ll, "\u25BA (9.8) Attention, pedestrian crosswalk", jsBuilder(p).attention("PEDESTRIAN"));
|
||||
addButton(ll, "\u25BA (9.9) Attention, tunnel", jsBuilder(p).attention("TUNNEL"));
|
||||
|
||||
addButton(ll, "Other prompts:", jsBuilder(p));
|
||||
addButton(ll, "\u25BA (10.1) GPS signal lost", jsBuilder(p).gpsLocationLost());
|
||||
addButton(ll, "\u25BA (10.2) GPS signal recovered", jsBuilder(p).gpsLocationRecover());
|
||||
addButton(ll, "\u25BA (10.3) You have been off the route for 1050m", jsBuilder(p).offRoute(1050));
|
||||
addButton(ll, "\u25BA (10.4) You are back on the route", jsBuilder(p).backOnRoute());
|
||||
|
||||
addButton(ll, "Voice system info:", jsBuilder(p));
|
||||
addButton(ll, "\u25BA (11.1) (Tap to refresh)\n" + getVoiceSystemInfo(), jsBuilder(p).attention(""));
|
||||
addButton(ll, "\u25BA (11.2) Tap to change Phone call audio delay (if car stereo cuts off prompts). Default is 1500\u00A0ms.", jsBuilder(p).attention(""));
|
||||
ll.forceLayout();
|
||||
}
|
||||
|
||||
private CommandBuilder builder(CommandPlayer p){
|
||||
return p.newCommandBuilder();
|
||||
}
|
||||
|
||||
private JSCommandBuilder jsBuilder(JSTTSCommandPlayerImpl p) {
|
||||
return p.newCommandBuilder();
|
||||
}
|
||||
|
||||
public void addButton(ViewGroup layout, final String description, final CommandBuilder builder){
|
||||
final Button button = new Button(this);
|
||||
button.setGravity(Gravity.LEFT);
|
||||
|
|
|
@ -28,42 +28,46 @@ public class JSVoiceRouter extends VoiceRouter {
|
|||
|
||||
// Issue 2377: Play Dest here only if not already previously announced, to avoid repetition
|
||||
if (includeDest == true) {
|
||||
result.put("toRef", getSpeakablePointName(i.getRef()));
|
||||
result.put("toStreetName", getSpeakablePointName(i.getStreetName()));
|
||||
result.put("toDest", getSpeakablePointName(i.getDestinationName()));
|
||||
result.put("toRef", getNonNullString(getSpeakablePointName(i.getRef())));
|
||||
result.put("toStreetName", getNonNullString(getSpeakablePointName(i.getStreetName())));
|
||||
result.put("toDest", getNonNullString(getSpeakablePointName(i.getDestinationName())));
|
||||
} else {
|
||||
result.put("toRef", getSpeakablePointName(i.getRef()));
|
||||
result.put("toStreetName", getSpeakablePointName(i.getStreetName()));
|
||||
result.put("toRef", getNonNullString(getSpeakablePointName(i.getRef())));
|
||||
result.put("toStreetName", getNonNullString(getSpeakablePointName(i.getStreetName())));
|
||||
result.put("toDest", "");
|
||||
}
|
||||
if (currentSegment != null) {
|
||||
// Issue 2377: Play Dest here only if not already previously announced, to avoid repetition
|
||||
if (includeDest == true) {
|
||||
RouteDataObject obj = currentSegment.getObject();
|
||||
result.put("fromRef", getSpeakablePointName(obj.getRef(settings.MAP_PREFERRED_LOCALE.get(),
|
||||
settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection())));
|
||||
result.put("fromStreetName", getSpeakablePointName(obj.getName(settings.MAP_PREFERRED_LOCALE.get(),
|
||||
settings.MAP_TRANSLITERATE_NAMES.get())));
|
||||
result.put("fromDest", getSpeakablePointName(obj.getDestinationName(settings.MAP_PREFERRED_LOCALE.get(),
|
||||
settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection())));
|
||||
result.put("fromRef", getNonNullString(getSpeakablePointName(obj.getRef(settings.MAP_PREFERRED_LOCALE.get(),
|
||||
settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection()))));
|
||||
result.put("fromStreetName", getNonNullString(getSpeakablePointName(obj.getName(settings.MAP_PREFERRED_LOCALE.get(),
|
||||
settings.MAP_TRANSLITERATE_NAMES.get()))));
|
||||
result.put("fromDest", getNonNullString(getSpeakablePointName(obj.getDestinationName(settings.MAP_PREFERRED_LOCALE.get(),
|
||||
settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection()))));
|
||||
} else {
|
||||
RouteDataObject obj = currentSegment.getObject();
|
||||
result.put("fromRef", getSpeakablePointName(obj.getRef(settings.MAP_PREFERRED_LOCALE.get(),
|
||||
settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection())));
|
||||
result.put("fromStreetName", getSpeakablePointName(obj.getName(settings.MAP_PREFERRED_LOCALE.get(),
|
||||
settings.MAP_TRANSLITERATE_NAMES.get())));
|
||||
result.put("fromRef", getNonNullString(getSpeakablePointName(obj.getRef(settings.MAP_PREFERRED_LOCALE.get(),
|
||||
settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection()))));
|
||||
result.put("fromStreetName", getNonNullString(getSpeakablePointName(obj.getName(settings.MAP_PREFERRED_LOCALE.get(),
|
||||
settings.MAP_TRANSLITERATE_NAMES.get()))));
|
||||
result.put("fromDest", "");
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
result.put("toRef", getSpeakablePointName(i.getRef()));
|
||||
result.put("toStreetName", getSpeakablePointName(i.getStreetName()));
|
||||
result.put("toRef", getNonNullString(getSpeakablePointName(i.getRef())));
|
||||
result.put("toStreetName", getNonNullString(getSpeakablePointName(i.getStreetName())));
|
||||
result.put("toDest", "");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getNonNullString(String speakablePointName) {
|
||||
return speakablePointName == null ? "" : speakablePointName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates status of voice guidance
|
||||
* @param currentLocation
|
||||
|
@ -235,10 +239,10 @@ public class JSVoiceRouter extends VoiceRouter {
|
|||
}
|
||||
if (t.getValue() == TurnType.TL || t.getValue() == TurnType.TSHL || t.getValue() == TurnType.TSLL
|
||||
|| t.getValue() == TurnType.TU || t.getValue() == TurnType.KL ) {
|
||||
play.then().bearLeft( getSpeakableJSStreetName(currentSegment, next, false));
|
||||
play.then().bearLeft(getSpeakableJSStreetName(currentSegment, next, false));
|
||||
} else if (t.getValue() == TurnType.TR || t.getValue() == TurnType.TSHR || t.getValue() == TurnType.TSLR
|
||||
|| t.getValue() == TurnType.TRU || t.getValue() == TurnType.KR) {
|
||||
play.then().bearRight( getSpeakableJSStreetName(currentSegment, next, false));
|
||||
play.then().bearRight(getSpeakableJSStreetName(currentSegment, next, false));
|
||||
}
|
||||
}
|
||||
if (isPlay) {
|
||||
|
|
|
@ -1,10 +1,50 @@
|
|||
package net.osmand.plus.voice;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
|
||||
import net.osmand.PlatformUtil;
|
||||
import net.osmand.StateChangedListener;
|
||||
import net.osmand.plus.ApplicationMode;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.api.AudioFocusHelper;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import alice.tuprolog.InvalidLibraryException;
|
||||
import alice.tuprolog.Prolog;
|
||||
import alice.tuprolog.Struct;
|
||||
import alice.tuprolog.Term;
|
||||
import alice.tuprolog.Var;
|
||||
|
||||
public abstract class AbstractJSCommandPlayer implements CommandPlayer, StateChangedListener<ApplicationMode> {
|
||||
private static final Log log = PlatformUtil.getLog(AbstractJSCommandPlayer.class);
|
||||
|
||||
protected String language = "";
|
||||
protected OsmandApplication ctx;
|
||||
protected int streamType;
|
||||
private ApplicationMode applicationMode;
|
||||
|
||||
public static boolean btScoStatus = false;
|
||||
private static AudioFocusHelper mAudioFocusHelper;
|
||||
public static String btScoInit = "";
|
||||
|
||||
protected AbstractJSCommandPlayer(OsmandApplication ctx, ApplicationMode applicationMode,
|
||||
String voiceProvider) {
|
||||
this.ctx = ctx;
|
||||
this.applicationMode = applicationMode;
|
||||
long time = System.currentTimeMillis();
|
||||
this.ctx = ctx;
|
||||
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Initializing prolog system : " + (System.currentTimeMillis() - time)); //$NON-NLS-1$
|
||||
}
|
||||
this.streamType = ctx.getSettings().AUDIO_STREAM_GUIDANCE.getModeValue(applicationMode);
|
||||
language = voiceProvider.substring(0, voiceProvider.indexOf("-"));
|
||||
}
|
||||
|
||||
public abstract class AbstractJSCommandPlayer implements CommandPlayer {
|
||||
@Override
|
||||
public String getCurrentVoice() {
|
||||
return null;
|
||||
|
@ -24,7 +64,11 @@ public abstract class AbstractJSCommandPlayer implements CommandPlayer {
|
|||
|
||||
@Override
|
||||
public void clear() {
|
||||
|
||||
if(ctx != null && ctx.getSettings() != null) {
|
||||
ctx.getSettings().APPLICATION_MODE.removeListener(this);
|
||||
}
|
||||
abandonAudioFocus();
|
||||
ctx = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -51,4 +95,70 @@ public abstract class AbstractJSCommandPlayer implements CommandPlayer {
|
|||
public void stop() {
|
||||
|
||||
}
|
||||
|
||||
protected synchronized void abandonAudioFocus() {
|
||||
log.debug("abandonAudioFocus");
|
||||
if ((ctx != null && ctx.getSettings().AUDIO_STREAM_GUIDANCE.getModeValue(applicationMode) == 0) || (btScoStatus == true)) {
|
||||
toggleBtSco(false);
|
||||
}
|
||||
if (ctx != null && mAudioFocusHelper != null) {
|
||||
mAudioFocusHelper.abandonFocus(ctx, applicationMode, streamType);
|
||||
}
|
||||
mAudioFocusHelper = null;
|
||||
}
|
||||
|
||||
private synchronized boolean toggleBtSco(boolean on) {
|
||||
// Hardy, 2016-07-03: Establish a low quality BT SCO (Synchronous Connection-Oriented) link to interrupt e.g. a car stereo FM radio
|
||||
if (on) {
|
||||
try {
|
||||
AudioManager mAudioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);
|
||||
if (mAudioManager == null || !mAudioManager.isBluetoothScoAvailableOffCall()) {
|
||||
btScoInit = "Reported not available.";
|
||||
return false;
|
||||
}
|
||||
mAudioManager.setMode(0);
|
||||
mAudioManager.startBluetoothSco();
|
||||
mAudioManager.setBluetoothScoOn(true);
|
||||
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
btScoStatus = true;
|
||||
} catch (Exception e) {
|
||||
System.out.println("Exception starting BT SCO " + e.getMessage() );
|
||||
btScoStatus = false;
|
||||
btScoInit = "Available, but not initializad.\n(" + e.getMessage() + ")";
|
||||
return false;
|
||||
}
|
||||
btScoInit = "Available, initialized OK.";
|
||||
return true;
|
||||
} else {
|
||||
AudioManager mAudioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);
|
||||
if (mAudioManager == null) {
|
||||
return false;
|
||||
}
|
||||
mAudioManager.setBluetoothScoOn(false);
|
||||
mAudioManager.stopBluetoothSco();
|
||||
mAudioManager.setMode(AudioManager.MODE_NORMAL);
|
||||
btScoStatus = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public ApplicationMode getApplicationMode() {
|
||||
return applicationMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(ApplicationMode change) {
|
||||
//
|
||||
// if(prologSystem != null) {
|
||||
// try {
|
||||
// prologSystem.getTheoryManager().retract(new Struct("appMode", new Var()));
|
||||
// } catch (Exception e) {
|
||||
// log.error("Retract error: ", e);
|
||||
// }
|
||||
// prologSystem.getTheoryManager()
|
||||
// .assertA(
|
||||
// new Struct("appMode", new Struct(ctx.getSettings().APPLICATION_MODE.get().getStringKey()
|
||||
// .toLowerCase())), true, "", true);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import org.mozilla.javascript.Function;
|
|||
import org.mozilla.javascript.NativeJSON;
|
||||
import org.mozilla.javascript.Scriptable;
|
||||
import org.mozilla.javascript.ScriptableObject;
|
||||
import org.mozilla.javascript.WrapFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -23,12 +22,14 @@ import java.util.Map;
|
|||
public class JSCommandBuilder extends CommandBuilder {
|
||||
|
||||
private static final Log log = PlatformUtil.getLog(JSCommandBuilder.class);
|
||||
private static final String SET_MODE = "setMode";
|
||||
private static final String SET_METRIC_CONST = "setMetricConst";
|
||||
|
||||
private Context jsContext;
|
||||
private List<String> listStruct = new ArrayList<>();
|
||||
ScriptableObject jsScope;
|
||||
private ScriptableObject jsScope;
|
||||
|
||||
public JSCommandBuilder(CommandPlayer commandPlayer) {
|
||||
JSCommandBuilder(CommandPlayer commandPlayer) {
|
||||
super(commandPlayer);
|
||||
}
|
||||
|
||||
|
@ -46,16 +47,16 @@ public class JSCommandBuilder extends CommandBuilder {
|
|||
|
||||
|
||||
private String readFileContents(String path) {
|
||||
FileInputStream fis = null;
|
||||
StringBuilder fileContent = new StringBuilder("");
|
||||
try {
|
||||
fis = new FileInputStream(new File(path));
|
||||
FileInputStream fis = new FileInputStream(new File(path));
|
||||
byte[] buffer = new byte[1024];
|
||||
int n;
|
||||
while ((n = fis.read(buffer)) != -1)
|
||||
{
|
||||
fileContent.append(new String(buffer, 0, n));
|
||||
}
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
|
@ -63,18 +64,20 @@ public class JSCommandBuilder extends CommandBuilder {
|
|||
}
|
||||
|
||||
public void setParameters(String metricCons, boolean tts) {
|
||||
// jsContext.property("setMode").toFunction().call(jsContext, tts);
|
||||
Object obj = jsScope.get("setMode", jsScope);
|
||||
Object mode = jsScope.get(SET_MODE, jsScope);
|
||||
Object metrics = jsScope.get(SET_METRIC_CONST, jsScope);
|
||||
callVoidJsFunction(mode, new Object[]{tts});
|
||||
callVoidJsFunction(metrics, new Object[]{metricCons});
|
||||
}
|
||||
|
||||
if (obj instanceof Function) {
|
||||
Function jsFunction = (Function) obj;
|
||||
jsFunction.call(jsContext, jsScope, jsScope, new Object[]{metricCons});
|
||||
jsFunction.call(jsContext, jsScope, jsScope, new Object[]{tts});
|
||||
private void callVoidJsFunction(Object function, Object[] params) {
|
||||
if (function instanceof Function) {
|
||||
Function jsFunction = (Function) function;
|
||||
jsFunction.call(jsContext, jsScope, jsScope, params);
|
||||
}
|
||||
}
|
||||
|
||||
private JSCommandBuilder addCommand(String name, Object... args){
|
||||
// TODO add JSCore
|
||||
Object obj = jsScope.get(name);
|
||||
if (obj instanceof Function) {
|
||||
Function jsFunction = (Function) obj;
|
||||
|
@ -97,7 +100,7 @@ public class JSCommandBuilder extends CommandBuilder {
|
|||
}
|
||||
|
||||
public JSCommandBuilder makeUT(Map<String, String> streetName){
|
||||
return addCommand(C_MAKE_UT, convertStreetName(streetName));
|
||||
return makeUT(-1, streetName);
|
||||
}
|
||||
@Override
|
||||
public JSCommandBuilder speedAlarm(int maxSpeed, float speed){
|
||||
|
@ -126,10 +129,10 @@ public class JSCommandBuilder extends CommandBuilder {
|
|||
|
||||
|
||||
public JSCommandBuilder turn(String param, Map<String, String> streetName) {
|
||||
return addCommand(C_TURN, param, convertStreetName(streetName));
|
||||
return turn(param, -1, streetName);
|
||||
}
|
||||
|
||||
public JSCommandBuilder turn(String param, double dist, Map<String, String> streetName){
|
||||
public JSCommandBuilder turn(String param, double dist, Map<String, String> streetName) {
|
||||
return addCommand(C_TURN, param, dist, convertStreetName(streetName));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package net.osmand.plus.voice;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
|
@ -8,21 +10,65 @@ import android.support.v7.app.AlertDialog;
|
|||
import android.util.Log;
|
||||
|
||||
import net.osmand.IndexConstants;
|
||||
import net.osmand.PlatformUtil;
|
||||
import net.osmand.plus.ApplicationMode;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.activities.SettingsActivity;
|
||||
import net.osmand.plus.routing.VoiceRouter;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
import org.mozilla.javascript.ScriptableObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class JSTTSCommandPlayerImpl extends AbstractJSCommandPlayer {
|
||||
private static final org.apache.commons.logging.Log log = PlatformUtil.getLog(JSTTSCommandPlayerImpl.class);
|
||||
|
||||
private static final String TAG = JSTTSCommandPlayerImpl.class.getSimpleName();
|
||||
private static TextToSpeech mTts;
|
||||
private static String ttsVoiceStatus = "";
|
||||
private static String ttsVoiceUsed = "";
|
||||
|
||||
private boolean speechAllowed = false;
|
||||
private Context mTtsContext;
|
||||
|
||||
private org.mozilla.javascript.Context jsContext;
|
||||
private ScriptableObject jsScope;
|
||||
|
||||
private float cSpeechRate = 1;
|
||||
|
||||
private static final class IntentStarter implements
|
||||
DialogInterface.OnClickListener {
|
||||
private final Context ctx;
|
||||
private final String intentAction;
|
||||
private final Uri intentData;
|
||||
|
||||
private IntentStarter(Context ctx, String intentAction) {
|
||||
this(ctx,intentAction, null);
|
||||
}
|
||||
|
||||
private IntentStarter(Context ctx, String intentAction, Uri intentData) {
|
||||
this.ctx = ctx;
|
||||
this.intentAction = intentAction;
|
||||
this.intentData = intentData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent installIntent = new Intent();
|
||||
installIntent.setAction(intentAction);
|
||||
if (intentData != null) {
|
||||
installIntent.setData(intentData);
|
||||
}
|
||||
ctx.startActivity(installIntent);
|
||||
}
|
||||
}
|
||||
private OsmandApplication app;
|
||||
private ApplicationMode appMode;
|
||||
private VoiceRouter vrt;
|
||||
|
@ -32,12 +78,142 @@ public class JSTTSCommandPlayerImpl extends AbstractJSCommandPlayer {
|
|||
|
||||
private static int ttsRequests = 0;
|
||||
|
||||
public JSTTSCommandPlayerImpl(OsmandApplication ctx, ApplicationMode applicationMode, VoiceRouter vrt, String voiceProvider) {
|
||||
this.app = ctx;
|
||||
public JSTTSCommandPlayerImpl(Activity ctx, ApplicationMode applicationMode, VoiceRouter vrt, String voiceProvider) throws CommandPlayerException {
|
||||
super((OsmandApplication) ctx.getApplication(), applicationMode, voiceProvider);
|
||||
this.app = (OsmandApplication) ctx.getApplicationContext();
|
||||
this.appMode = applicationMode;
|
||||
this.vrt = vrt;
|
||||
this.voiceProvider = voiceProvider;
|
||||
mTts = new TextToSpeech(ctx, null);
|
||||
if (Algorithms.isEmpty(language)) {
|
||||
throw new CommandPlayerException(
|
||||
ctx.getString(R.string.voice_data_corrupted));
|
||||
}
|
||||
OsmandApplication app = (OsmandApplication) ctx.getApplicationContext();
|
||||
if(app.accessibilityEnabled()) {
|
||||
cSpeechRate = app.getSettings().SPEECH_RATE.get();
|
||||
}
|
||||
initializeEngine(app, ctx);
|
||||
params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, app.getSettings().AUDIO_STREAM_GUIDANCE
|
||||
.getModeValue(getApplicationMode()).toString());
|
||||
setJSContext(app.getAppPath(IndexConstants.VOICE_INDEX_DIR).getAbsolutePath() + "/" + voiceProvider + "/" + language + "_tts.js");
|
||||
}
|
||||
|
||||
private void initializeEngine(final Context ctx, final Activity act) {
|
||||
if (mTtsContext != ctx) {
|
||||
internalClear();
|
||||
}
|
||||
if (mTts == null) {
|
||||
mTtsContext = ctx;
|
||||
ttsVoiceStatus = "";
|
||||
ttsVoiceUsed = "";
|
||||
ttsRequests = 0;
|
||||
final float speechRate = cSpeechRate;
|
||||
|
||||
final String[] lsplit = (language + "____.").split("[_\\-]");
|
||||
// constructor supports lang_country_variant
|
||||
Locale newLocale0 = new Locale(lsplit[0], lsplit[1], lsplit[2]);
|
||||
// #3344: Try Locale builder instead of constructor (only available from API 21). Also supports script (for now supported as trailing x_x_x_Scrp)
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21) {
|
||||
try {
|
||||
newLocale0 = new Locale.Builder().setLanguage(lsplit[0]).setScript(lsplit[3]).setRegion(lsplit[1]).setVariant(lsplit[2]).build();
|
||||
} catch (RuntimeException e) {
|
||||
// Falls back to constructor
|
||||
}
|
||||
}
|
||||
final Locale newLocale = newLocale0;
|
||||
|
||||
mTts = new TextToSpeech(ctx, new TextToSpeech.OnInitListener() {
|
||||
@Override
|
||||
public void onInit(int status) {
|
||||
if (status != TextToSpeech.SUCCESS) {
|
||||
ttsVoiceStatus = "NO INIT SUCCESS";
|
||||
internalClear();
|
||||
} else if (mTts != null) {
|
||||
speechAllowed = true;
|
||||
switch (mTts.isLanguageAvailable(newLocale)) {
|
||||
case TextToSpeech.LANG_MISSING_DATA:
|
||||
if (isSettingsActivity(act)) {
|
||||
AlertDialog.Builder builder = createAlertDialog(
|
||||
R.string.tts_missing_language_data_title,
|
||||
R.string.tts_missing_language_data,
|
||||
new JSTTSCommandPlayerImpl.IntentStarter(
|
||||
act,
|
||||
TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA),
|
||||
act);
|
||||
builder.show();
|
||||
}
|
||||
ttsVoiceStatus = newLocale.getDisplayName() + ": LANG_MISSING_DATA";
|
||||
ttsVoiceUsed = getVoiceUsed();
|
||||
break;
|
||||
case TextToSpeech.LANG_AVAILABLE:
|
||||
ttsVoiceStatus = newLocale.getDisplayName() + ": LANG_AVAILABLE";
|
||||
case TextToSpeech.LANG_COUNTRY_AVAILABLE:
|
||||
ttsVoiceStatus = "".equals(ttsVoiceStatus) ? newLocale.getDisplayName() + ": LANG_COUNTRY_AVAILABLE" : ttsVoiceStatus;
|
||||
case TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE:
|
||||
try {
|
||||
mTts.setLanguage(newLocale);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
mTts.setLanguage(Locale.getDefault());
|
||||
}
|
||||
if(speechRate != 1) {
|
||||
mTts.setSpeechRate(speechRate);
|
||||
}
|
||||
ttsVoiceStatus = "".equals(ttsVoiceStatus) ? newLocale.getDisplayName() + ": LANG_COUNTRY_VAR_AVAILABLE" : ttsVoiceStatus;
|
||||
ttsVoiceUsed = getVoiceUsed();
|
||||
break;
|
||||
case TextToSpeech.LANG_NOT_SUPPORTED:
|
||||
//maybe weird, but I didn't want to introduce parameter in around 5 methods just to do this if condition
|
||||
if (isSettingsActivity(act)) {
|
||||
AlertDialog.Builder builder = createAlertDialog(
|
||||
R.string.tts_language_not_supported_title,
|
||||
R.string.tts_language_not_supported,
|
||||
new JSTTSCommandPlayerImpl.IntentStarter(
|
||||
act,
|
||||
Intent.ACTION_VIEW, Uri.parse("market://search?q=text to speech engine"
|
||||
)),
|
||||
act);
|
||||
builder.show();
|
||||
}
|
||||
ttsVoiceStatus = newLocale.getDisplayName() + ": LANG_NOT_SUPPORTED";
|
||||
ttsVoiceUsed = getVoiceUsed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSettingsActivity(final Context ctx) {
|
||||
return ctx instanceof SettingsActivity;
|
||||
}
|
||||
|
||||
private String getVoiceUsed() {
|
||||
try {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21) {
|
||||
if (mTts.getVoice() != null) {
|
||||
return mTts.getVoice().toString() + " (API " + android.os.Build.VERSION.SDK_INT + ")";
|
||||
}
|
||||
} else {
|
||||
return mTts.getLanguage() + " (API " + android.os.Build.VERSION.SDK_INT + " only reports language)";
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
// mTts.getVoice() might throw NPE
|
||||
}
|
||||
return "-";
|
||||
}
|
||||
});
|
||||
mTts.setOnUtteranceCompletedListener(new TextToSpeech.OnUtteranceCompletedListener() {
|
||||
// The call back is on a binder thread.
|
||||
@Override
|
||||
public synchronized void onUtteranceCompleted(String utteranceId) {
|
||||
if (--ttsRequests <= 0)
|
||||
abandonAudioFocus();
|
||||
log.debug("ttsRequests="+ttsRequests);
|
||||
if (ttsRequests < 0) {
|
||||
ttsRequests = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -48,11 +224,36 @@ public class JSTTSCommandPlayerImpl extends AbstractJSCommandPlayer {
|
|||
@Override
|
||||
public JSCommandBuilder newCommandBuilder() {
|
||||
JSCommandBuilder commandBuilder = new JSCommandBuilder(this);
|
||||
commandBuilder.setJSContext(app.getAppPath(IndexConstants.VOICE_INDEX_DIR).getAbsolutePath() + "/" + voiceProvider + "/en_tts.js");
|
||||
commandBuilder.setParameters(app.getSettings().METRIC_SYSTEM.get().toHumanString(app), true);
|
||||
commandBuilder.setJSContext(jsContext, jsScope);
|
||||
commandBuilder.setParameters(app.getSettings().METRIC_SYSTEM.get().toTTSString(), true);
|
||||
return commandBuilder;
|
||||
}
|
||||
|
||||
private String readFileContents(String path) {
|
||||
StringBuilder fileContent = new StringBuilder("");
|
||||
try {
|
||||
FileInputStream fis = new FileInputStream(new File(path));
|
||||
byte[] buffer = new byte[1024];
|
||||
int n;
|
||||
while ((n = fis.read(buffer)) != -1)
|
||||
{
|
||||
fileContent.append(new String(buffer, 0, n));
|
||||
}
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
return fileContent.toString();
|
||||
}
|
||||
|
||||
private void setJSContext(String path) {
|
||||
String script = readFileContents(path);
|
||||
jsContext = org.mozilla.javascript.Context.enter();
|
||||
jsContext.setOptimizationLevel(-1);
|
||||
jsScope = jsContext.initStandardObjects();
|
||||
jsContext.evaluateString(jsScope, script, "JS", 1, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playCommands(CommandBuilder builder) {
|
||||
final List<String> execute = builder.execute(); //list of strings, the speech text, play it
|
||||
|
@ -60,7 +261,7 @@ public class JSTTSCommandPlayerImpl extends AbstractJSCommandPlayer {
|
|||
for (String s : execute) {
|
||||
bld.append(s).append(' ');
|
||||
}
|
||||
if (mTts != null && !vrt.isMute()) {
|
||||
if (mTts != null && !vrt.isMute() && speechAllowed) {
|
||||
if (ttsRequests++ == 0) {
|
||||
// Delay first prompt of each batch to allow BT SCO connection being established
|
||||
if (app.getSettings().AUDIO_STREAM_GUIDANCE.getModeValue(appMode) == 0) {
|
||||
|
@ -83,18 +284,37 @@ public class JSTTSCommandPlayerImpl extends AbstractJSCommandPlayer {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
public void stop(){
|
||||
ttsRequests = 0;
|
||||
if (mTts != null){
|
||||
mTts.stop();
|
||||
}
|
||||
abandonAudioFocus();
|
||||
}
|
||||
|
||||
public static String getTtsVoiceStatus() {
|
||||
return ttsVoiceStatus;
|
||||
}
|
||||
|
||||
public static String getTtsVoiceUsed() {
|
||||
return ttsVoiceUsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
super.clear();
|
||||
internalClear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAudioStream(int streamType) {
|
||||
|
||||
super.updateAudioStream(streamType);
|
||||
params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, streamType+"");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLanguage() {
|
||||
return "en";
|
||||
return language;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -102,8 +322,28 @@ public class JSTTSCommandPlayerImpl extends AbstractJSCommandPlayer {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
|
||||
private void internalClear() {
|
||||
ttsRequests = 0;
|
||||
speechAllowed = false;
|
||||
if (mTts != null) {
|
||||
mTts.shutdown();
|
||||
mTts = null;
|
||||
}
|
||||
abandonAudioFocus();
|
||||
mTtsContext = null;
|
||||
ttsVoiceStatus = "";
|
||||
ttsVoiceUsed = "";
|
||||
}
|
||||
|
||||
private AlertDialog.Builder createAlertDialog(int titleResID, int messageResID,
|
||||
JSTTSCommandPlayerImpl.IntentStarter intentStarter, final Activity ctx) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setCancelable(true);
|
||||
builder.setNegativeButton(R.string.shared_string_no, null);
|
||||
builder.setPositiveButton(R.string.shared_string_yes, intentStarter);
|
||||
builder.setTitle(titleResID);
|
||||
builder.setMessage(messageResID);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue