diff --git a/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java b/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java
index a4f69ecbc7..35f996d87a 100644
--- a/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java
+++ b/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java
@@ -34,7 +34,8 @@ public class IndexConstants {
public static final String GEN_LOG_EXT = ".gen.log"; //$NON-NLS-1$
public static final String VOICE_INDEX_EXT_ZIP = ".voice.zip"; //$NON-NLS-1$
- public static final String TTSVOICE_INDEX_EXT_ZIP = ".ttsvoice.zip"; //$NON-NLS-1$
+ public static final String TTSVOICE_INDEX_EXT_ZIP = ".ttsvoice.zip";
+ public static final String TTSVOICE_INDEX_EXT_JS = "tts.js";
public static final String ANYVOICE_INDEX_EXT_ZIP = "voice.zip"; //$NON-NLS-1$ //to cactch both voices, .voice.zip and .ttsvoice.zip
public static final String FONT_INDEX_EXT = ".otf"; //$NON-NLS-1$
@@ -67,9 +68,4 @@ public class IndexConstants {
public static final String VOICE_INDEX_DIR = "voice/"; //$NON-NLS-1$
public static final String RENDERERS_DIR = "rendering/"; //$NON-NLS-1$
public static final String ROUTING_XML_FILE= "routing.xml";
-
-
-
-
-
}
diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidUtils.kt b/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidUtils.kt
index 297fb05aa6..d9b78ae239 100644
--- a/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidUtils.kt
+++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidUtils.kt
@@ -113,9 +113,9 @@ object AndroidUtils {
}
}
- fun isGooglePlayInstalled(ctx: Context): Boolean {
+ fun isAppInstalled(ctx: Context, appPackage: String): Boolean {
try {
- ctx.packageManager.getPackageInfo("com.android.vending", 0)
+ ctx.packageManager.getPackageInfo(appPackage, 0)
} catch (e: PackageManager.NameNotFoundException) {
return false
}
@@ -127,7 +127,7 @@ object AndroidUtils {
Intent(Intent.ACTION_VIEW, Uri.parse(AndroidUtils.getPlayMarketLink(ctx, packageName)))
fun getPlayMarketLink(ctx: Context, packageName: String): String {
- if (isGooglePlayInstalled(ctx)) {
+ if (isAppInstalled(ctx, "com.android.vending")) {
return "market://details?id=$packageName"
}
return "https://play.google.com/store/apps/details?id=$packageName"
diff --git a/OsmAnd/assets/bundled_assets.xml b/OsmAnd/assets/bundled_assets.xml
index c4f408b34e..5f805aa19a 100644
--- a/OsmAnd/assets/bundled_assets.xml
+++ b/OsmAnd/assets/bundled_assets.xml
@@ -43,6 +43,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle
index b5d90b93c3..9d4e503d8f 100644
--- a/OsmAnd/build.gradle
+++ b/OsmAnd/build.gradle
@@ -238,6 +238,7 @@ task collectVoiceAssets(type: Sync) {
from "../../resources/voice"
into "assets/voice"
include "**/*.p"
+ include "**/*.js"
}
task cleanNoTranslate(type: Delete) {
@@ -361,6 +362,9 @@ dependencies {
implementation 'com.vividsolutions:jts-core:1.14.0'
implementation 'com.squareup.picasso:picasso:2.71828'
+ // JS core
+ implementation group: 'org.mozilla', name: 'rhino', version: '1.7.9'
+
// size restrictions
// implementation 'com.ibm.icu:icu4j:50.1'
// implementation 'net.sf.trove4j:trove4j:3.0.3'
diff --git a/OsmAnd/res/layout/search_dialog_fragment.xml b/OsmAnd/res/layout/search_dialog_fragment.xml
index 52de3ba07e..2d8374ba03 100644
--- a/OsmAnd/res/layout/search_dialog_fragment.xml
+++ b/OsmAnd/res/layout/search_dialog_fragment.xml
@@ -58,7 +58,7 @@
android:gravity="center_vertical"
android:lines="1"
android:singleLine="true"
- android:text="5 selected"
+ tools:text="5 selected"
android:textColor="@color/color_white"
android:textSize="@dimen/default_list_text_size_large"/>
@@ -264,7 +264,7 @@
android:paddingLeft="@dimen/context_menu_padding_margin_small"
android:paddingRight="@dimen/context_menu_padding_margin_small"
android:paddingTop="@dimen/context_menu_padding_margin_small"
- android:text="Отправить"
+ android:text="@string/shared_string_send"
android:textColor="?attr/wikivoyage_active_color"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium" />
diff --git a/OsmAnd/res/values-cs/phrases.xml b/OsmAnd/res/values-cs/phrases.xml
index c562363705..c9fedb8c2b 100644
--- a/OsmAnd/res/values-cs/phrases.xml
+++ b/OsmAnd/res/values-cs/phrases.xml
@@ -3763,6 +3763,8 @@
Platební centrum
Služba převodu peněz
- Sport: Ano
- Sport: Ne
+ Sportovní horolezectví: Ano
+ Sportovní horolezectví: Ne
+ Ledovec typu ledová čepice
+ Ledovec typu ledové pole
diff --git a/OsmAnd/res/values-es-rAR/phrases.xml b/OsmAnd/res/values-es-rAR/phrases.xml
index 0b469b467c..e5442b22a1 100644
--- a/OsmAnd/res/values-es-rAR/phrases.xml
+++ b/OsmAnd/res/values-es-rAR/phrases.xml
@@ -3707,7 +3707,7 @@
Piso de oficina
Centro de pagos
- Transferencia de dinero
+ Transferencia de dinero;Giros de dinero
Subte
diff --git a/OsmAnd/res/values-es-rUS/phrases.xml b/OsmAnd/res/values-es-rUS/phrases.xml
index bcd73a8789..5be72a498b 100644
--- a/OsmAnd/res/values-es-rUS/phrases.xml
+++ b/OsmAnd/res/values-es-rUS/phrases.xml
@@ -3469,7 +3469,7 @@
Piso de oficina
Centro de pagos
- Transferencia de dinero
+ Transferencia de dinero; Giros de dinero
Metro
diff --git a/OsmAnd/res/values-is/strings.xml b/OsmAnd/res/values-is/strings.xml
index fd967be7dc..3869205f7a 100644
--- a/OsmAnd/res/values-is/strings.xml
+++ b/OsmAnd/res/values-is/strings.xml
@@ -2956,4 +2956,9 @@ Stendur fyrir svæði: %1$s x %2$s
Engar niðurstöður?
\nSegðu okkur meira um þetta.
Senda leitarfyrirspurn?
+ Takk fyrir umsögn þína
+ Hnútur eða leið fannst ekki.
+ Engar leitarniðurstöður?
+\nLáttu okkur vita
+ Sendi leið inn…
diff --git a/OsmAnd/res/values-ja/strings.xml b/OsmAnd/res/values-ja/strings.xml
index e8857d45bf..e151b676af 100644
--- a/OsmAnd/res/values-ja/strings.xml
+++ b/OsmAnd/res/values-ja/strings.xml
@@ -2961,7 +2961,7 @@ OsmAndとOSMをサポートする方法として現状最適な方法である
マーカーとして追加する経由地点を経路データから選択してください。(経由地点を含む経路のみリストアップされます)
マーカーとして追加するお気に入りのカテゴリを選択してください。
- お気に入り地点や経路データ内の経由地点をまとめてインポートできます。
+ お気に入り地点やGPX経由地点をグループとしてインポートできます。
グループでインポート
お気に入り地点や経路データ内の経由地点をマーカーとしてまとめて追加できます。
二つ
@@ -3016,7 +3016,7 @@ OsmAndとOSMをサポートする方法として現状最適な方法である
グループの追加
OSM編集はPOIの作成や更新、注釈の追加などが可能です。記録したGPXファイルを用いてあなたもOSMに貢献することができるでしょう。
通過済みにする
- GPX経路に追加
+ GPXファイルに追加
現在
その他
@@ -3050,4 +3050,8 @@ OsmAndとOSMをサポートする方法として現状最適な方法である
標準的な汎用スタイルです。人口密集地の場合描写の簡素化がなされます。主な機能としては等高線、ルート、路面品質、通行制限、道路標識、SACスケールの通路描写をサポートし、急流下りなどのウォータースポーツにも使用できます。
Wikipediaをオンライン参照
このリンクを開くとウェブブラウザで閲覧することができます。
+ GPXファイルとしてインポート
+ GPXファイルに追加しよう
+ GPXファイルまたは記録した経路からインポートします。
+ GPXファイルからインポート
diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml
index 3eb6c83c35..d0d6dfce4e 100644
--- a/OsmAnd/res/values/strings.xml
+++ b/OsmAnd/res/values/strings.xml
@@ -2931,4 +2931,5 @@
Searching for the corresponding wiki article
Article not found
How to open Wikipedia articles?
+ Tap a button and listen to the corresponding voice prompt to identify missing or faulty prompts.
diff --git a/OsmAnd/src/net/osmand/plus/AppInitializer.java b/OsmAnd/src/net/osmand/plus/AppInitializer.java
index 02b129ced4..8c84c0d85d 100644
--- a/OsmAnd/src/net/osmand/plus/AppInitializer.java
+++ b/OsmAnd/src/net/osmand/plus/AppInitializer.java
@@ -46,6 +46,8 @@ import net.osmand.plus.search.QuickSearchHelper;
import net.osmand.plus.views.corenative.NativeCoreContext;
import net.osmand.plus.voice.CommandPlayer;
import net.osmand.plus.voice.CommandPlayerException;
+import net.osmand.plus.voice.JSMediaCommandPlayerImpl;
+import net.osmand.plus.voice.JSTTSCommandPlayerImpl;
import net.osmand.plus.voice.MediaCommandPlayerImpl;
import net.osmand.plus.voice.TTSCommandPlayerImpl;
import net.osmand.plus.wikivoyage.data.TravelDbHelper;
@@ -577,9 +579,12 @@ public class AppInitializer implements IProgress {
if (!voiceDir.exists()) {
throw new CommandPlayerException(ctx.getString(R.string.voice_data_unavailable));
}
-
- if (MediaCommandPlayerImpl.isMyData(voiceDir)) {
- return new MediaCommandPlayerImpl(osmandApplication, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
+ boolean useJs = app.getSettings().USE_JS_VOICE_GUIDANCE.get();
+ if (useJs && JSTTSCommandPlayerImpl.isMyData(voiceDir)) {
+ return new JSTTSCommandPlayerImpl(ctx, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
+ } else if (MediaCommandPlayerImpl.isMyData(voiceDir)) {
+ return useJs && JSMediaCommandPlayerImpl.isMyData(voiceDir) ? new JSMediaCommandPlayerImpl(osmandApplication, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider)
+ : new MediaCommandPlayerImpl(osmandApplication, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
} else if (TTSCommandPlayerImpl.isMyData(voiceDir)) {
return new TTSCommandPlayerImpl(ctx, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
}
diff --git a/OsmAnd/src/net/osmand/plus/OsmandSettings.java b/OsmAnd/src/net/osmand/plus/OsmandSettings.java
index c1cb454bc2..3d7cf09ed2 100644
--- a/OsmAnd/src/net/osmand/plus/OsmandSettings.java
+++ b/OsmAnd/src/net/osmand/plus/OsmandSettings.java
@@ -1346,6 +1346,7 @@ public class OsmandSettings {
public final OsmandPreference ANIMATE_MY_LOCATION = new BooleanPreference("animate_my_location", true).makeGlobal().cache();
+ public final OsmandPreference USE_JS_VOICE_GUIDANCE = new BooleanPreference("use_js_voice_guidance", false);
public final OsmandPreference ROUTE_MAP_MARKERS_START_MY_LOC = new BooleanPreference("route_map_markers_start_my_loc", false).makeGlobal().cache();
public final OsmandPreference ROUTE_MAP_MARKERS_ROUND_TRIP = new BooleanPreference("route_map_markers_round_trip", false).makeGlobal().cache();
diff --git a/OsmAnd/src/net/osmand/plus/activities/LocalIndexHelper.java b/OsmAnd/src/net/osmand/plus/activities/LocalIndexHelper.java
index 01822460a5..d87f2f76cd 100644
--- a/OsmAnd/src/net/osmand/plus/activities/LocalIndexHelper.java
+++ b/OsmAnd/src/net/osmand/plus/activities/LocalIndexHelper.java
@@ -13,6 +13,7 @@ import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.SQLiteTileSource;
import net.osmand.plus.download.ui.AbstractLoadLocalIndexTask;
+import net.osmand.plus.voice.JSTTSCommandPlayerImpl;
import net.osmand.plus.voice.MediaCommandPlayerImpl;
import net.osmand.plus.voice.TTSCommandPlayerImpl;
import net.osmand.util.Algorithms;
@@ -215,11 +216,12 @@ public class LocalIndexHelper {
private void loadVoiceData(File voiceDir, List result, boolean backup, AbstractLoadLocalIndexTask loadTask) {
if (voiceDir.canRead()) {
+ boolean useJs = app.getSettings().USE_JS_VOICE_GUIDANCE.get();
//First list TTS files, they are preferred
for (File voiceF : listFilesSorted(voiceDir)) {
- if (voiceF.isDirectory() && !MediaCommandPlayerImpl.isMyData(voiceF) && (Build.VERSION.SDK_INT >= 4)) {
+ if (voiceF.isDirectory() && !MediaCommandPlayerImpl.isMyData(voiceF)) {
LocalIndexInfo info = null;
- if (TTSCommandPlayerImpl.isMyData(voiceF)) {
+ if ((TTSCommandPlayerImpl.isMyData(voiceF) && !useJs) || (JSTTSCommandPlayerImpl.isMyData(voiceF) && useJs)) {
info = new LocalIndexInfo(LocalIndexType.TTS_VOICE_DATA, voiceF, backup, app);
}
if (info != null) {
diff --git a/OsmAnd/src/net/osmand/plus/activities/SettingsNavigationActivity.java b/OsmAnd/src/net/osmand/plus/activities/SettingsNavigationActivity.java
index 1942e5f598..79c75a4456 100644
--- a/OsmAnd/src/net/osmand/plus/activities/SettingsNavigationActivity.java
+++ b/OsmAnd/src/net/osmand/plus/activities/SettingsNavigationActivity.java
@@ -52,6 +52,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import static net.osmand.plus.mapcontextmenu.other.RoutePreferencesMenu.getVoiceFiles;
+
public class SettingsNavigationActivity extends SettingsBaseActivity {
public static final String MORE_VALUE = "MORE_VALUE";
@@ -244,7 +246,7 @@ public class SettingsNavigationActivity extends SettingsBaseActivity {
private void reloadVoiceListPreference(PreferenceScreen screen) {
String[] entries;
String[] entrieValues;
- Set voiceFiles = getVoiceFiles();
+ Set voiceFiles = getVoiceFiles(this);
entries = new String[voiceFiles.size() + 2];
entrieValues = new String[voiceFiles.size() + 2];
int k = 0;
@@ -263,20 +265,6 @@ public class SettingsNavigationActivity extends SettingsBaseActivity {
}
- private Set getVoiceFiles() {
- // read available voice data
- File extStorage = getMyApplication().getAppPath(IndexConstants.VOICE_INDEX_DIR);
- Set setFiles = new LinkedHashSet();
- if (extStorage.exists()) {
- for (File f : extStorage.listFiles()) {
- if (f.isDirectory()) {
- setFiles.add(f.getName());
- }
- }
- }
- return setFiles;
- }
-
private void addVoicePrefs(PreferenceGroup cat) {
if (!Version.isBlackberry((OsmandApplication) getApplication())) {
ListPreference lp = createListPreference(
diff --git a/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java b/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java
index bece3ea19a..70665ad117 100644
--- a/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java
+++ b/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java
@@ -14,6 +14,7 @@ import android.preference.PreferenceScreen;
import android.support.v7.app.AlertDialog;
import android.view.View;
+import net.osmand.StateChangedListener;
import net.osmand.plus.ApplicationMode;
import net.osmand.plus.OsmAndLocationSimulation;
import net.osmand.plus.OsmandApplication;
@@ -32,6 +33,7 @@ import java.util.Set;
public class SettingsDevelopmentActivity extends SettingsBaseActivity {
+ private StateChangedListener useJSVoiceGuidanceListener;
@SuppressLint("SimpleDateFormat")
@Override
@@ -62,6 +64,15 @@ public class SettingsDevelopmentActivity extends SettingsBaseActivity {
cat.addPreference(createCheckBoxPreference(settings.ANIMATE_MY_LOCATION,
R.string.animate_my_location,
R.string.animate_my_location_desc));
+ useJSVoiceGuidanceListener = new StateChangedListener() {
+ @Override
+ public void stateChanged(Boolean change) {
+ getMyApplication().getDownloadThread().runReloadIndexFilesSilent();
+ }
+ };
+ cat.addPreference(createCheckBoxPreference(settings.USE_JS_VOICE_GUIDANCE, "Use JS voice guidance",
+ "Use new voice guidance logic based on JavaScript"));
+ settings.USE_JS_VOICE_GUIDANCE.addListener(useJSVoiceGuidanceListener);
final Preference firstRunPreference = new Preference(this);
firstRunPreference.setTitle(R.string.simulate_initial_startup);
@@ -249,6 +260,9 @@ public class SettingsDevelopmentActivity extends SettingsBaseActivity {
b.show();
}
-
-
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ settings.USE_JS_VOICE_GUIDANCE.removeListener(useJSVoiceGuidanceListener);
+ }
}
diff --git a/OsmAnd/src/net/osmand/plus/development/TestVoiceActivity.java b/OsmAnd/src/net/osmand/plus/development/TestVoiceActivity.java
index f968488b7e..fd5951b010 100644
--- a/OsmAnd/src/net/osmand/plus/development/TestVoiceActivity.java
+++ b/OsmAnd/src/net/osmand/plus/development/TestVoiceActivity.java
@@ -22,6 +22,9 @@ 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.JSMediaCommandPlayerImpl;
+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,12 +32,16 @@ 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;
import alice.tuprolog.Term;
+import static net.osmand.plus.mapcontextmenu.other.RoutePreferencesMenu.getVoiceFiles;
+
/**
* Test Voice activity
@@ -62,7 +69,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,28 +82,15 @@ 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);
}
-
- private Set getVoiceFiles() {
- // read available voice data
- File extStorage = ((OsmandApplication) getApplication()).getAppPath(IndexConstants.VOICE_INDEX_DIR);
- Set setFiles = new LinkedHashSet();
- if (extStorage.exists()) {
- for (File f : extStorage.listFiles()) {
- if (f.isDirectory()) {
- setFiles.add(f.getName());
- }
- }
- }
- return setFiles;
- }
+
private void selectVoice(final LinearLayout ll) {
String[] entries;
final String[] entrieValues;
- Set voiceFiles = getVoiceFiles();
+ Set voiceFiles = getVoiceFiles(this);
entries = new String[voiceFiles.size() ];
entrieValues = new String[voiceFiles.size() ];
int k = 0;
@@ -216,51 +210,72 @@ public class TestVoiceActivity extends OsmandActionBarActivity {
}
private void addButtons(final LinearLayout ll, CommandPlayer p) {
+ boolean isJS = p instanceof JSTTSCommandPlayerImpl || p instanceof JSMediaCommandPlayerImpl;
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));
- addButton(ll, "\u25BA (1.3) New route calculated 3700m, 7320sec (02:02)", builder(p).newRouteCalculated(3700, 7320));
- addButton(ll, "\u25BA (1.4) New route calculated 9100m, 10980sec (03:03)", builder(p).newRouteCalculated(9100, 10980));
- addButton(ll, "\u25BA (2.1) Route recalculated 11500m, 18600sec (05:10)", builder(p).routeRecalculated(11500, 18600));
- addButton(ll, "\u25BA (2.2) Route recalculated 19633m, 26700sec (07:25)", builder(p).routeRecalculated(19633, 26700));
- addButton(ll, "\u25BA (2.3) Route recalculated 89750m, 55800sec (15:30)", builder(p).routeRecalculated(89750, 55800));
- addButton(ll, "\u25BA (2.4) Route recalculated 125900m, 92700sec (25:45)", builder(p).routeRecalculated(125900, 92700));
+ addButton(ll, "\u25BA (1.1) New route calculated, 150m, 230sec (00:03:50)", !isJS ? builder(p).newRouteCalculated(150, 230) : jsBuilder(p).newRouteCalculated(150, 230));
+ addButton(ll, "\u25BA (1.2) New route calculated, 1350m, 3680sec (01:01:20)", !isJS ? builder(p).newRouteCalculated(1350, 3680) : jsBuilder(p).newRouteCalculated(1350, 3680));
+ addButton(ll, "\u25BA (1.3) New route calculated 3700m, 7320sec (02:02)", !isJS ? builder(p).newRouteCalculated(3700, 7320) : jsBuilder(p).newRouteCalculated(3700, 7320));
+ addButton(ll, "\u25BA (1.4) New route calculated 9100m, 10980sec (03:03)", !isJS ? builder(p).newRouteCalculated(9100, 10980) : jsBuilder(p).newRouteCalculated(9100, 10980));
+ addButton(ll, "\u25BA (2.1) Route recalculated 11500m, 18600sec (05:10)", !isJS ? builder(p).routeRecalculated(11500, 18600) : jsBuilder(p).routeRecalculated(11500, 18600));
+ addButton(ll, "\u25BA (2.2) Route recalculated 19633m, 26700sec (07:25)", !isJS ? builder(p).routeRecalculated(19633, 26700) : jsBuilder(p).routeRecalculated(19633, 26700));
+ addButton(ll, "\u25BA (2.3) Route recalculated 89750m, 55800sec (15:30)", !isJS ? builder(p).routeRecalculated(89750, 55800) : jsBuilder(p).routeRecalculated(89750, 55800));
+ addButton(ll, "\u25BA (2.4) Route recalculated 125900m, 92700sec (25:45)", !isJS ? builder(p).routeRecalculated(125900, 92700) : jsBuilder(p).routeRecalculated(125900, 92700));
addButton(ll, "All turn types: prepareTurn, makeTurnIn, turn:", builder(p));
- addButton(ll, "\u25BA (3.1) After 1520m turn slightly left", builder(p).prepareTurn(AbstractPrologCommandPlayer.A_LEFT_SL, 1520, street(p, "")));
- addButton(ll, "\u25BA (3.2) In 450m turn sharply left onto 'Hauptstra"+"\u00df"+"e', then bear right", builder(p).turn(AbstractPrologCommandPlayer.A_LEFT_SH, 450, street(p, "Hauptstraße")).then().bearRight(street(p, "")));
- addButton(ll, "\u25BA (3.3) Turn left, then in 100m turn slightly right", builder(p).turn(AbstractPrologCommandPlayer.A_LEFT, street(p, "")).then().turn(AbstractPrologCommandPlayer.A_RIGHT_SL, 100, street(p, "")));
- addButton(ll, "\u25BA (3.4) After 3100m turn right onto 'SR 80' toward 'Rome'", builder(p).prepareTurn(AbstractPrologCommandPlayer.A_RIGHT, 3100, street(p, "", "SR 80", "Rome")));
- addButton(ll, "\u25BA (3.5) In 370m turn slightly right onto 'Route 23' 'Main Street', then bear left", builder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SL, 370, street(p, "Main Street", "Route 23")).then().bearLeft(street(p, "")));
- addButton(ll, "\u25BA (3.6) Turn sharply right onto 'Dr.-Quinn-Stra"+"\u00df"+"e'", builder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SH, street(p, "Dr.-Quinn-Straße")));
+ addButton(ll, "\u25BA (3.1) After 1520m turn slightly left", !isJS ? builder(p).prepareTurn(AbstractPrologCommandPlayer.A_LEFT_SL, 1520, street(p, "")) :
+ 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", !isJS ? builder(p).turn(AbstractPrologCommandPlayer.A_LEFT_SH, 450, street(p, "Hauptstraße")).then().bearRight(street(p, "")) :
+ 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", !isJS ? builder(p).turn(AbstractPrologCommandPlayer.A_LEFT, street(p, "")).then().turn(AbstractPrologCommandPlayer.A_RIGHT_SL, 100, street(p, "")) :
+ 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'", !isJS ? builder(p).prepareTurn(AbstractPrologCommandPlayer.A_RIGHT, 3100, street(p, "", "SR 80", "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 Street', then bear left", !isJS ? builder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SL, 370, street(p, "Main Street", "Route 23")).then().bearLeft(street(p, "")) :
+ 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'", !isJS ? builder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SH, street(p, "Dr.-Quinn-Straße")) :
+ jsBuilder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SH, jsStreet(p, "Dr.-Quinn-Straße")));
addButton(ll, "Keep left/right: prepareTurn, makeTurnIn, turn:", builder(p));
- addButton(ll, "\u25BA (4.1) After 1810m keep left ' '", builder(p).prepareTurn(AbstractPrologCommandPlayer.A_LEFT_KEEP, 1810, street(p, "")));
- addButton(ll, "\u25BA (4.2) In 400m keep left ' ' then in 80m keep right onto 'A1'", builder(p).turn(AbstractPrologCommandPlayer.A_LEFT_KEEP, 400, street(p, "")).then().turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, 80, street(p,"", "A1")));
- addButton(ll, "\u25BA (4.3) Keep right on 'Highway 60'", builder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, street(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'",
+ addButton(ll, "\u25BA (4.1) After 1810m keep left ' '", !isJS ? builder(p).prepareTurn(AbstractPrologCommandPlayer.A_LEFT_KEEP, 1810, street(p, "")) :
+ 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'", !isJS ? builder(p).turn(AbstractPrologCommandPlayer.A_LEFT_KEEP, 400, street(p, "")).then().turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, 80, street(p,"", "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'", !isJS ? builder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, street(p, "Highway 60", "", "", "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'", !isJS ?
builder(p).turn(AbstractPrologCommandPlayer.A_LEFT, street(p, "Broadway"))
- .then().turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, 100, street(p, "")).andArriveAtDestination("Town Hall"));
+ .then().turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, 100, street(p, "")).andArriveAtDestination("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:", builder(p));
- addButton(ll, "\u25BA (5.1) After 1250m enter a roundabout", builder(p).prepareRoundAbout(1250, 3, street(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'", builder(p).roundAbout(450, 0, 1, street(p,"", "I 15", "Los Angeles")));
- addButton(ll, "\u25BA (5.3) Roundabout: Take the 2nd exit onto 'Highway 60'", builder(p).roundAbout(0, 2, street(p, "Highway 60")));
+ addButton(ll, "\u25BA (5.1) After 1250m enter a roundabout", !isJS ? builder(p).prepareRoundAbout(1250, 3, street(p,"", "I 15", "Los Angeles")) :
+ 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'", isJS ? builder(p).roundAbout(450, 0, 1, street(p,"", "I 15", "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'", !isJS ? builder(p).roundAbout(0, 2, street(p, "Highway 60")) :
+ jsBuilder(p).roundAbout(0, 2, jsStreet(p, "Highway 60")));
addButton(ll, "U-turns: prepareTurn, makeTurnIn, turn, when possible:", builder(p));
- addButton(ll, "\u25BA (6.1) After 640m make a U-turn", builder(p).prepareMakeUT(640, street(p, "")));
- addButton(ll, "\u25BA (6.2) In 400m make a U-turn", builder(p).makeUT(400, street(p, "")));
- addButton(ll, "\u25BA (6.3) Make a U-turn on 'Riviera'", builder(p).makeUT(street(p, "Riviera", "", "", "Riviera")));
+ addButton(ll, "\u25BA (6.1) After 640m make a U-turn", !isJS ? builder(p).prepareMakeUT(640, street(p, "")) :
+ jsBuilder(p).prepareMakeUT(640, jsStreet(p, "")));
+ addButton(ll, "\u25BA (6.2) In 400m make a U-turn", !isJS ? builder(p).makeUT(400, street(p, "")) :
+ jsBuilder(p).makeUT(400, jsStreet(p, "")));
+ addButton(ll, "\u25BA (6.3) Make a U-turn on 'Riviera'", !isJS ? builder(p).makeUT(street(p, "Riviera", "", "", "Riviera")) :
+ jsBuilder(p).makeUT(jsStreet(p, "Riviera", "", "", "Riviera")));
addButton(ll, "\u25BA (6.4) When possible, make a U-turn", builder(p).makeUTwp());
addButton(ll, "Go straight, follow the road, approaching:", builder(p));
addButton(ll, "\u25BA (7.1) Straight ahead", builder(p).goAhead());
- addButton(ll, "\u25BA (7.2) Continue for 2350m to ' '", builder(p).goAhead(2350, street(p, "")));
- addButton(ll, "\u25BA (7.3) Continue for 360m to 'Broadway' and arrive at your intermediate destination ' '", builder(p).goAhead(360, street(p,"Broadway")).andArriveAtIntermediatePoint(""));
- addButton(ll, "\u25BA (7.4) Continue for 800m to 'Dr Martin Luther King Jr Boulevard' and arrive at your destination ' '", builder(p).goAhead(800, street(p,"", "Dr Martin Luther King Jr Boulevard")).andArriveAtDestination(""));
- addButton(ll, "\u25BA (7.5) Continue for 200m and pass GPX waypoint 'Trailhead'", builder(p).goAhead(200, null).andArriveAtWayPoint("Trailhead"));
- addButton(ll, "\u25BA (7.6) Continue for 400m and pass favorite 'Brewery'", builder(p).goAhead(400, null).andArriveAtFavorite("Brewery"));
- addButton(ll, "\u25BA (7.7) Continue for 600m and pass POI 'Museum'", builder(p).goAhead(600, null).andArriveAtPoi("Museum"));
+ addButton(ll, "\u25BA (7.2) Continue for 2350m to ' '", !isJS ? builder(p).goAhead(2350, street(p, "")) :
+ jsBuilder(p).goAhead(2350, jsStreet(p, "")));
+ addButton(ll, "\u25BA (7.3) Continue for 360m to 'Broadway' and arrive at your intermediate destination ' '", !isJS ? builder(p).goAhead(360, street(p,"Broadway")).andArriveAtIntermediatePoint("") :
+ 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 ' '", !isJS ? builder(p).goAhead(800, street(p,"", "Dr Martin Luther King Jr Boulevard")).andArriveAtDestination("") :
+ 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'", !isJS ? builder(p).goAhead(200, null).andArriveAtWayPoint("Trailhead") : jsBuilder(p).goAhead(200, new HashMap()).andArriveAtWayPoint("Trailhead"));
+ addButton(ll, "\u25BA (7.6) Continue for 400m and pass favorite 'Brewery'", !isJS ? builder(p).goAhead(400, null).andArriveAtFavorite("Brewery") : jsBuilder(p).goAhead(400, new HashMap()).andArriveAtFavorite("Brewery"));
+ addButton(ll, "\u25BA (7.7) Continue for 600m and pass POI 'Museum'", !isJS ? builder(p).goAhead(600, null).andArriveAtPoi("Museum") : jsBuilder(p).goAhead(600, new HashMap()).andArriveAtPoi("Museum"));
addButton(ll, "Arriving and passing points:", builder(p));
addButton(ll, "\u25BA (8.1) Arrive at your destination 'Home'", builder(p).arrivedAtDestination("Home"));
@@ -292,10 +307,31 @@ public class TestVoiceActivity extends OsmandActionBarActivity {
ll.forceLayout();
}
+ private Map jsStreet(CommandPlayer p, String... args) {
+ Map 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 CommandBuilder builder(CommandPlayer p){
return p.newCommandBuilder();
}
+ private JSCommandBuilder jsBuilder(CommandPlayer p) {
+ return (JSCommandBuilder) p.newCommandBuilder();
+ }
+
public void addButton(ViewGroup layout, final String description, final CommandBuilder builder){
final Button button = new Button(this);
button.setGravity(Gravity.LEFT);
diff --git a/OsmAnd/src/net/osmand/plus/download/DownloadActivityType.java b/OsmAnd/src/net/osmand/plus/download/DownloadActivityType.java
index 582c18b841..4576fd654e 100644
--- a/OsmAnd/src/net/osmand/plus/download/DownloadActivityType.java
+++ b/OsmAnd/src/net/osmand/plus/download/DownloadActivityType.java
@@ -298,6 +298,8 @@ public class DownloadActivityType {
return FileNameTranslationHelper.getVoiceName(ctx, getBasename(indexItem));
} else if (fileName.endsWith(IndexConstants.TTSVOICE_INDEX_EXT_ZIP)) {
return FileNameTranslationHelper.getVoiceName(ctx, getBasename(indexItem));
+ } else if (fileName.endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS)) {
+ return FileNameTranslationHelper.getVoiceName(ctx, getBasename(indexItem));
}
return getBasename(indexItem);
}
@@ -336,6 +338,9 @@ public class DownloadActivityType {
// if(fileName.endsWith(IndexConstants.VOICE_INDEX_EXT_ZIP) ||
// fileName.endsWith(IndexConstants.TTSVOICE_INDEX_EXT_ZIP)) {
if (this == VOICE_FILE) {
+ if (fileName.contains(".js")) {
+ return fileName.replace('_', '-').replaceAll(".js", "");
+ }
int l = fileName.lastIndexOf('_');
if (l == -1) {
l = fileName.length();
@@ -408,7 +413,8 @@ public class DownloadActivityType {
if (l == -1) {
l = fileName.length();
}
- return fileName.substring(0, l);
+ return fileName.endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS) ? fileName.replace('_', '-')
+ .replaceAll(".js", "") : fileName.substring(0, l);
}
if (this == FONT_FILE) {
int l = fileName.indexOf('.');
diff --git a/OsmAnd/src/net/osmand/plus/download/DownloadOsmandIndexesHelper.java b/OsmAnd/src/net/osmand/plus/download/DownloadOsmandIndexesHelper.java
index 822bfe91fc..2927a3f579 100644
--- a/OsmAnd/src/net/osmand/plus/download/DownloadOsmandIndexesHelper.java
+++ b/OsmAnd/src/net/osmand/plus/download/DownloadOsmandIndexesHelper.java
@@ -165,6 +165,13 @@ public class DownloadOsmandIndexesHelper {
result.add(new AssetIndexItem(voice + ext, "voice", date, dateModified, "0.1", destFile.length(), key,
destFile.getPath(), DownloadActivityType.VOICE_FILE));
+ } else if (target.endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS) && target.startsWith("voice/")) {
+ String lang = target.substring("voice/".length(), target.indexOf("-"));
+ File destFile = new File(voicePath, target.substring("voice/".length(),
+ target.indexOf("/", "voice/".length())) + "/" + lang + "_tts.js");
+ result.add(new AssetIndexItem(lang + "_" + IndexConstants.TTSVOICE_INDEX_EXT_JS,
+ "voice", date, dateModified, "0.1", destFile.length(), key,
+ destFile.getPath(), DownloadActivityType.VOICE_FILE));
}
}
result.sort();
diff --git a/OsmAnd/src/net/osmand/plus/download/DownloadResourceGroup.java b/OsmAnd/src/net/osmand/plus/download/DownloadResourceGroup.java
index d95d45acd1..fa0125e8e4 100644
--- a/OsmAnd/src/net/osmand/plus/download/DownloadResourceGroup.java
+++ b/OsmAnd/src/net/osmand/plus/download/DownloadResourceGroup.java
@@ -83,7 +83,8 @@ public class DownloadResourceGroup {
}
public boolean isHeader() {
- return this == VOICE_HEADER_REC || this == VOICE_HEADER_TTS || this == SUBREGIONS
+ return this == VOICE_HEADER_REC || this == VOICE_HEADER_TTS
+ || this == SUBREGIONS
|| this == WORLD_MAPS || this == REGION_MAPS || this == OTHER_GROUP
|| this == HILLSHADE_HEADER || this == SRTM_HEADER
|| this == OTHER_MAPS_HEADER || this == OTHER_MAPS_GROUP
diff --git a/OsmAnd/src/net/osmand/plus/download/DownloadResources.java b/OsmAnd/src/net/osmand/plus/download/DownloadResources.java
index 62b211e687..80d7063e28 100644
--- a/OsmAnd/src/net/osmand/plus/download/DownloadResources.java
+++ b/OsmAnd/src/net/osmand/plus/download/DownloadResources.java
@@ -316,11 +316,14 @@ public class DownloadResources extends DownloadResourceGroup {
Map > groupByRegion = new LinkedHashMap>();
OsmandRegions regs = app.getRegions();
+ boolean useJS = app.getSettings().USE_JS_VOICE_GUIDANCE.get();
for (IndexItem ii : resources) {
if (ii.getType() == DownloadActivityType.VOICE_FILE) {
- if (ii.getFileName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_ZIP)) {
+ if (ii.getFileName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_ZIP) && !useJS) {
voiceTTS.addItem(ii);
- } else {
+ } else if (ii.getFileName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS) && useJS){
+ voiceTTS.addItem(ii);
+ } else if (ii.getFileName().endsWith(IndexConstants.VOICE_INDEX_EXT_ZIP)){
voiceRec.addItem(ii);
}
continue;
diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/RoutePreferencesMenu.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/RoutePreferencesMenu.java
index 070c0a61c6..c40e66a608 100644
--- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/RoutePreferencesMenu.java
+++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/RoutePreferencesMenu.java
@@ -1,5 +1,6 @@
package net.osmand.plus.mapcontextmenu.other;
+import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -329,20 +330,36 @@ public class RoutePreferencesMenu {
app.initVoiceCommandPlayer(mapActivity, app.getRoutingHelper().getAppMode(), false, null, true, false);
}
- private static Set getVoiceFiles(MapActivity mapActivity) {
+ public static Set getVoiceFiles(Activity activity) {
// read available voice data
- File extStorage = mapActivity.getMyApplication().getAppPath(IndexConstants.VOICE_INDEX_DIR);
- Set setFiles = new LinkedHashSet<>();
+ OsmandApplication app = ((OsmandApplication) activity.getApplication());
+ File extStorage = app.getAppPath(IndexConstants.VOICE_INDEX_DIR);
+ Set setFiles = new LinkedHashSet();
+ boolean addJS = app.getSettings().USE_JS_VOICE_GUIDANCE.get();
if (extStorage.exists()) {
for (File f : extStorage.listFiles()) {
if (f.isDirectory()) {
- setFiles.add(f.getName());
+ if (addJS && hasJavaScript(f)) {
+ setFiles.add(f.getName());
+ } else if (!addJS) {
+ setFiles.add(f.getName());
+ }
+
}
}
}
return setFiles;
}
+ private static boolean hasJavaScript(File f) {
+ for (File file : f.listFiles()) {
+ if (file.getName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public OnItemClickListener getItemClickListener(final ArrayAdapter> listAdapter) {
return new OnItemClickListener() {
@Override
diff --git a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java
index cd85fd9d15..04bb769fcb 100644
--- a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java
+++ b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java
@@ -386,8 +386,11 @@ public class ResourceManager {
for (File f : lf) {
if (f.isDirectory()) {
File conf = new File(f, "_config.p");
+ boolean useJS = context.getSettings().USE_JS_VOICE_GUIDANCE.get();
if (!conf.exists()) {
- conf = new File(f, "_ttsconfig.p");
+ String lang = f.getName().replace("-tts", "");
+ conf = useJS ? new File(f, lang + "_" + IndexConstants.TTSVOICE_INDEX_EXT_JS) :
+ new File(f, "_ttsconfig.p");
}
if (conf.exists()) {
indexFileNames.put(f.getName(), dateFormat.format(conf.lastModified())); //$NON-NLS-1$
diff --git a/OsmAnd/src/net/osmand/plus/routing/JSVoiceRouter.java b/OsmAnd/src/net/osmand/plus/routing/JSVoiceRouter.java
new file mode 100644
index 0000000000..393c39957a
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/routing/JSVoiceRouter.java
@@ -0,0 +1,330 @@
+package net.osmand.plus.routing;
+
+import net.osmand.Location;
+import net.osmand.binary.RouteDataObject;
+import net.osmand.plus.OsmandSettings;
+import net.osmand.plus.voice.CommandBuilder;
+import net.osmand.plus.voice.JSCommandBuilder;
+import net.osmand.router.RouteSegmentResult;
+import net.osmand.router.TurnType;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+
+public class JSVoiceRouter extends VoiceRouter {
+
+ public JSVoiceRouter(RoutingHelper router, OsmandSettings settings) {
+ super(router, settings);
+ }
+
+ public Map getSpeakableJSStreetName(RouteSegmentResult currentSegment, RouteDirectionInfo i, boolean includeDest) {
+ Map result = new HashMap<>();
+ if (i == null || !router.getSettings().SPEAK_STREET_NAMES.get()) {
+ return result;
+ }
+ if (player != null && player.supportsStructuredStreetNames()) {
+
+ // Issue 2377: Play Dest here only if not already previously announced, to avoid repetition
+ if (includeDest == true) {
+ 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", 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", 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", 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", 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
+ */
+ @Override
+ protected void updateStatus(Location currentLocation, boolean repeat) {
+ // Directly after turn: goAhead (dist), unless:
+ // < PREPARE_LONG_DISTANCE (e.g. 3500m): playPrepareTurn (-not played any more-)
+ // < PREPARE_DISTANCE (e.g. 1500m): playPrepareTurn ("Turn after ...")
+ // < TURN_IN_DISTANCE (e.g. 390m or 30sec): playMakeTurnIn ("Turn in ...")
+ // < TURN_DISTANCE (e.g. 50m or 7sec): playMakeTurn ("Turn ...")
+ float speed = DEFAULT_SPEED;
+ if (currentLocation != null && currentLocation.hasSpeed()) {
+ speed = Math.max(currentLocation.getSpeed(), speed);
+ }
+
+ RouteCalculationResult.NextDirectionInfo nextInfo = router.getNextRouteDirectionInfo(new RouteCalculationResult.NextDirectionInfo(), true);
+ RouteSegmentResult currentSegment = router.getCurrentSegmentResult();
+ if (nextInfo == null || nextInfo.directionInfo == null) {
+ return;
+ }
+ int dist = nextInfo.distanceTo;
+ RouteDirectionInfo next = nextInfo.directionInfo;
+
+ // If routing is changed update status to unknown
+ if (next != nextRouteDirection) {
+ nextRouteDirection = next;
+ currentStatus = STATUS_UNKNOWN;
+ suppressDest = false;
+ playedAndArriveAtTarget = false;
+ announceBackOnRoute = false;
+ if (playGoAheadDist != -1) {
+ playGoAheadDist = 0;
+ }
+ }
+
+ if (!repeat) {
+ if (dist <= 0) {
+ return;
+ } else if (needsInforming()) {
+ playGoAhead(dist, getSpeakableJSStreetName(currentSegment, next, false));
+ return;
+ } else if (currentStatus == STATUS_TOLD) {
+ // nothing said possibly that's wrong case we should say before that
+ // however it should be checked manually ?
+ return;
+ }
+ }
+
+ if (currentStatus == STATUS_UNKNOWN) {
+ // Play "Continue for ..." if (1) after route calculation no other prompt is due, or (2) after a turn if next turn is more than PREPARE_LONG_DISTANCE away
+ if ((playGoAheadDist == -1) || (dist > PREPARE_LONG_DISTANCE)) {
+ playGoAheadDist = dist - 3 * TURN_DISTANCE;
+ }
+ }
+
+ RouteCalculationResult.NextDirectionInfo nextNextInfo = router.getNextRouteDirectionInfoAfter(nextInfo, new RouteCalculationResult.NextDirectionInfo(), true); //I think "true" is correct here, not "!repeat"
+ // Note: getNextRouteDirectionInfoAfter(nextInfo, x, y).distanceTo is distance from nextInfo, not from current position!
+
+ // STATUS_TURN = "Turn (now)"
+ if ((repeat || statusNotPassed(STATUS_TURN)) && isDistanceLess(speed, dist, TURN_DISTANCE, TURN_DEFAULT_SPEED)) {
+ if (nextNextInfo.distanceTo < TURN_IN_DISTANCE_END && nextNextInfo != null) {
+ playMakeTurn(currentSegment, next, nextNextInfo);
+ } else {
+ playMakeTurn(currentSegment, next, null);
+ }
+ if (!next.getTurnType().goAhead() && isTargetPoint(nextNextInfo)) { // !goAhead() avoids isolated "and arrive.." prompt, as goAhead() is not pronounced
+ if (nextNextInfo.distanceTo < TURN_IN_DISTANCE_END) {
+ // Issue #2865: Ensure a distance associated with the destination arrival is always announced, either here, or in subsequent "Turn in" prompt
+ // Distance fon non-straights already announced in "Turn (now)"'s nextnext code above
+ if ((nextNextInfo != null) && (nextNextInfo.directionInfo != null) && nextNextInfo.directionInfo.getTurnType().goAhead()) {
+ playThen();
+ playGoAhead(nextNextInfo.distanceTo, new HashMap());
+ }
+ playAndArriveAtDestination(nextNextInfo);
+ } else if (nextNextInfo.distanceTo < 1.2f * TURN_IN_DISTANCE_END) {
+ // 1.2 is safety margin should the subsequent "Turn in" prompt not fit in amy more
+ playThen();
+ playGoAhead(nextNextInfo.distanceTo, new HashMap());
+ playAndArriveAtDestination(nextNextInfo);
+ }
+ }
+ nextStatusAfter(STATUS_TURN);
+
+ // STATUS_TURN_IN = "Turn in ..."
+ } else if ((repeat || statusNotPassed(STATUS_TURN_IN)) && isDistanceLess(speed, dist, TURN_IN_DISTANCE, 0f)) {
+ if (repeat || dist >= TURN_IN_DISTANCE_END) {
+ if ((isDistanceLess(speed, nextNextInfo.distanceTo, TURN_DISTANCE, 0f) || nextNextInfo.distanceTo < TURN_IN_DISTANCE_END) &&
+ nextNextInfo != null) {
+ playMakeTurnIn(currentSegment, next, dist - (int) btScoDelayDistance, nextNextInfo.directionInfo);
+ } else {
+ playMakeTurnIn(currentSegment, next, dist - (int) btScoDelayDistance, null);
+ }
+ playGoAndArriveAtDestination(repeat, nextInfo, currentSegment);
+ }
+ nextStatusAfter(STATUS_TURN_IN);
+
+ // STATUS_PREPARE = "Turn after ..."
+ } else if ((repeat || statusNotPassed(STATUS_PREPARE)) && (dist <= PREPARE_DISTANCE)) {
+ if (repeat || dist >= PREPARE_DISTANCE_END) {
+ if (!repeat && (next.getTurnType().keepLeft() || next.getTurnType().keepRight())) {
+ // Do not play prepare for keep left/right
+ } else {
+ playPrepareTurn(currentSegment, next, dist);
+ playGoAndArriveAtDestination(repeat, nextInfo, currentSegment);
+ }
+ }
+ nextStatusAfter(STATUS_PREPARE);
+
+ // STATUS_LONG_PREPARE = also "Turn after ...", we skip this now, users said this is obsolete
+ } else if ((repeat || statusNotPassed(STATUS_LONG_PREPARE)) && (dist <= PREPARE_LONG_DISTANCE)) {
+ if (repeat || dist >= PREPARE_LONG_DISTANCE_END) {
+ playPrepareTurn(currentSegment, next, dist);
+ playGoAndArriveAtDestination(repeat, nextInfo, currentSegment);
+ }
+ nextStatusAfter(STATUS_LONG_PREPARE);
+
+ // STATUS_UNKNOWN = "Continue for ..." if (1) after route calculation no other prompt is due, or (2) after a turn if next turn is more than PREPARE_LONG_DISTANCE away
+ } else if (statusNotPassed(STATUS_UNKNOWN)) {
+ // Strange how we get here but
+ nextStatusAfter(STATUS_UNKNOWN);
+ } else if (repeat || (statusNotPassed(STATUS_PREPARE) && dist < playGoAheadDist)) {
+ playGoAheadDist = 0;
+ playGoAhead(dist, getSpeakableJSStreetName(currentSegment, next, false));
+ }
+ }
+
+ private void playPrepareTurn(RouteSegmentResult currentSegment, RouteDirectionInfo next, int dist) {
+ JSCommandBuilder play = (JSCommandBuilder) getNewCommandPlayerToPlay();
+ if (play != null) {
+ String tParam = getTurnType(next.getTurnType());
+ if (tParam != null) {
+ notifyOnVoiceMessage();
+ play.prepareTurn(tParam, dist, getSpeakableJSStreetName(currentSegment, next, true)).play();
+ } else if (next.getTurnType().isRoundAbout()) {
+ notifyOnVoiceMessage();
+ play.prepareRoundAbout(dist, next.getTurnType().getExitOut(), getSpeakableJSStreetName(currentSegment, next, true)).play();
+ } else if (next.getTurnType().getValue() == TurnType.TU || next.getTurnType().getValue() == TurnType.TRU) {
+ notifyOnVoiceMessage();
+ play.prepareMakeUT(dist, getSpeakableJSStreetName(currentSegment, next, true)).play();
+ }
+ }
+ }
+
+ private void playMakeTurnIn(RouteSegmentResult currentSegment, RouteDirectionInfo next, int dist, RouteDirectionInfo pronounceNextNext) {
+ JSCommandBuilder play = (JSCommandBuilder) getNewCommandPlayerToPlay();
+ if (play != null) {
+ String tParam = getTurnType(next.getTurnType());
+ boolean isPlay = true;
+ if (tParam != null) {
+ play.turn(tParam, dist, getSpeakableJSStreetName(currentSegment, next, true));
+ suppressDest = true;
+ } else if (next.getTurnType().isRoundAbout()) {
+ play.roundAbout(dist, next.getTurnType().getTurnAngle(), next.getTurnType().getExitOut(), getSpeakableJSStreetName(currentSegment, next, true));
+ // Other than in prepareTurn, in prepareRoundabout we do not announce destination, so we can repeat it one more time
+ suppressDest = false;
+ } else if (next.getTurnType().getValue() == TurnType.TU || next.getTurnType().getValue() == TurnType.TRU) {
+ play.makeUT(dist, getSpeakableJSStreetName(currentSegment, next, true));
+ suppressDest = true;
+ } else {
+ isPlay = false;
+ }
+ // 'then keep' preparation for next after next. (Also announces an interim straight segment, which is not pronounced above.)
+ if (pronounceNextNext != null) {
+ TurnType t = pronounceNextNext.getTurnType();
+ isPlay = true;
+ if (t.getValue() != TurnType.C && next.getTurnType().getValue() == TurnType.C) {
+ play.goAhead(dist, getSpeakableJSStreetName(currentSegment, next, true));
+ }
+ 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));
+ } 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));
+ }
+ }
+ if (isPlay) {
+ notifyOnVoiceMessage();
+ play.play();
+ }
+ }
+ }
+
+ private void playGoAhead(int dist, Map streetName) {
+ CommandBuilder play = getNewCommandPlayerToPlay();
+ JSCommandBuilder playJs = (JSCommandBuilder) play;
+ if (play != null) {
+ notifyOnVoiceMessage();
+ playJs.goAhead(dist, streetName).play();
+ }
+ }
+
+ private void playMakeTurn(RouteSegmentResult currentSegment, RouteDirectionInfo next, RouteCalculationResult.NextDirectionInfo nextNextInfo) {
+ JSCommandBuilder play = (JSCommandBuilder) getNewCommandPlayerToPlay();
+ if (play != null) {
+ String tParam = getTurnType(next.getTurnType());
+ boolean isplay = true;
+ if (tParam != null) {
+ play.turn(tParam, getSpeakableJSStreetName(currentSegment, next, !suppressDest));
+ } else if (next.getTurnType().isRoundAbout()) {
+ play.roundAbout(next.getTurnType().getTurnAngle(), next.getTurnType().getExitOut(), getSpeakableJSStreetName(currentSegment, next, !suppressDest));
+ } else if (next.getTurnType().getValue() == TurnType.TU || next.getTurnType().getValue() == TurnType.TRU) {
+ play.makeUT(getSpeakableJSStreetName(currentSegment, next, !suppressDest));
+ // Do not announce goAheads
+ //} else if (next.getTurnType().getValue() == TurnType.C)) {
+ // play.goAhead();
+ } else {
+ isplay = false;
+ }
+ // Add turn after next
+ if ((nextNextInfo != null) && (nextNextInfo.directionInfo != null)) {
+
+ // This case only needed should we want a prompt at the end of straight segments (equivalent of makeTurn) when nextNextInfo should be announced again there.
+ if (nextNextInfo.directionInfo.getTurnType().getValue() != TurnType.C && next.getTurnType().getValue() == TurnType.C) {
+ play.goAhead();
+ isplay = true;
+ }
+
+ String t2Param = getTurnType(nextNextInfo.directionInfo.getTurnType());
+ if (t2Param != null) {
+ if (isplay) {
+ play.then();
+ play.turn(t2Param, nextNextInfo.distanceTo, new HashMap());
+ }
+ } else if (nextNextInfo.directionInfo.getTurnType().isRoundAbout()) {
+ if (isplay) {
+ play.then();
+ play.roundAbout(nextNextInfo.distanceTo, nextNextInfo.directionInfo.getTurnType().getTurnAngle(),
+ nextNextInfo.directionInfo.getTurnType().getExitOut(), new HashMap());
+ }
+ } else if (nextNextInfo.directionInfo.getTurnType().getValue() == TurnType.TU) {
+ if (isplay) {
+ play.then();
+ play.makeUT(nextNextInfo.distanceTo, new HashMap());
+ }
+ }
+ }
+ if (isplay) {
+ notifyOnVoiceMessage();
+ play.play();
+ }
+ }
+ }
+
+ private void playGoAndArriveAtDestination(boolean repeat, RouteCalculationResult.NextDirectionInfo nextInfo, RouteSegmentResult currentSegment) {
+ RouteDirectionInfo next = nextInfo.directionInfo;
+ if (isTargetPoint(nextInfo) && (!playedAndArriveAtTarget || repeat)) {
+ if (next.getTurnType().goAhead()) {
+ playGoAhead(nextInfo.distanceTo, getSpeakableJSStreetName(currentSegment, next, false));
+ playAndArriveAtDestination(nextInfo);
+ playedAndArriveAtTarget = true;
+ } else if (nextInfo.distanceTo <= 2 * TURN_IN_DISTANCE) {
+ playAndArriveAtDestination(nextInfo);
+ playedAndArriveAtTarget = true;
+ }
+ }
+ }
+
+}
diff --git a/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java b/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java
index fa15b79b5d..f246cbac77 100644
--- a/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java
+++ b/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java
@@ -97,7 +97,9 @@ public class RoutingHelper {
public RoutingHelper(OsmandApplication context){
this.app = context;
settings = context.getSettings();
- voiceRouter = new VoiceRouter(this, settings);
+ boolean useJS = settings.USE_JS_VOICE_GUIDANCE.get();
+ voiceRouter = useJS ? new JSVoiceRouter(this, settings)
+ : new VoiceRouter(this, settings);
provider = new RouteProvider();
setAppMode(settings.APPLICATION_MODE.get());
}
diff --git a/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java b/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java
index af27e575ea..6ef3f69def 100644
--- a/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java
+++ b/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java
@@ -28,27 +28,27 @@ import android.media.SoundPool;
public class VoiceRouter {
private static final int STATUS_UTWP_TOLD = -1;
- private static final int STATUS_UNKNOWN = 0;
- private static final int STATUS_LONG_PREPARE = 1;
- private static final int STATUS_PREPARE = 2;
- private static final int STATUS_TURN_IN = 3;
- private static final int STATUS_TURN = 4;
- private static final int STATUS_TOLD = 5;
+ static final int STATUS_UNKNOWN = 0;
+ static final int STATUS_LONG_PREPARE = 1;
+ static final int STATUS_PREPARE = 2;
+ static final int STATUS_TURN_IN = 3;
+ static final int STATUS_TURN = 4;
+ static final int STATUS_TOLD = 5;
- private final RoutingHelper router;
- private static CommandPlayer player;
- private final OsmandSettings settings;
+ protected final RoutingHelper router;
+ protected static CommandPlayer player;
+ protected final OsmandSettings settings;
private static boolean mute = false;
- private static int currentStatus = STATUS_UNKNOWN;
- private static boolean playedAndArriveAtTarget = false;
- private static float playGoAheadDist = 0;
+ static int currentStatus = STATUS_UNKNOWN;
+ static boolean playedAndArriveAtTarget = false;
+ static float playGoAheadDist = 0;
private static long lastAnnouncedSpeedLimit = 0;
private static long waitAnnouncedSpeedLimit = 0;
private static long lastAnnouncedOffRoute = 0;
private static long waitAnnouncedOffRoute = 0;
- private static boolean suppressDest = false;
- private static boolean announceBackOnRoute = false;
+ static boolean suppressDest = false;
+ static boolean announceBackOnRoute = false;
// private static long lastTimeRouteRecalcAnnounced = 0;
// Remember when last announcement was made
private static long lastAnnouncement = 0;
@@ -66,9 +66,11 @@ public class VoiceRouter {
protected int TURN_DISTANCE = 0;
protected static VoiceCommandPending pendingCommand = null;
- private static RouteDirectionInfo nextRouteDirection;
+ static RouteDirectionInfo nextRouteDirection;
private Term empty;
+ private boolean useJS;
+
public interface VoiceMessageListener {
void onVoiceMessage();
}
@@ -78,6 +80,7 @@ public class VoiceRouter {
public VoiceRouter(RoutingHelper router, final OsmandSettings settings) {
this.router = router;
this.settings = settings;
+ useJS = settings.USE_JS_VOICE_GUIDANCE.get();
this.mute = settings.VOICE_MUTE.get();
empty = new Struct("");
voiceMessageListeners = new ConcurrentHashMap();
@@ -164,7 +167,7 @@ public class VoiceRouter {
}
}
- private double btScoDelayDistance = 0;
+ double btScoDelayDistance = 0;
public boolean isDistanceLess(float currentSpeed, double dist, double etalon, float defSpeed) {
if (defSpeed <= 0) {
@@ -201,7 +204,7 @@ public class VoiceRouter {
}
}
- private void nextStatusAfter(int previousStatus) {
+ void nextStatusAfter(int previousStatus) {
//STATUS_UNKNOWN=0 -> STATUS_LONG_PREPARE=1 -> STATUS_PREPARE=2 -> STATUS_TURN_IN=3 -> STATUS_TURN=4 -> STATUS_TOLD=5
if (previousStatus != STATUS_TOLD) {
this.currentStatus = previousStatus + 1;
@@ -210,7 +213,7 @@ public class VoiceRouter {
}
}
- private boolean statusNotPassed(int statusToCheck) {
+ boolean statusNotPassed(int statusToCheck) {
return currentStatus <= statusToCheck;
}
@@ -395,14 +398,14 @@ public class VoiceRouter {
}
}
- private boolean isTargetPoint(NextDirectionInfo info) {
+ boolean isTargetPoint(NextDirectionInfo info) {
boolean in = info != null && info.intermediatePoint;
boolean target = info == null || info.directionInfo == null
|| info.directionInfo.distance == 0;
return in || target;
}
- private boolean needsInforming() {
+ boolean needsInforming() {
final Integer repeat = settings.KEEP_INFORMING.get();
if (repeat == null || repeat == 0) return false;
@@ -557,7 +560,7 @@ public class VoiceRouter {
return false;
}
- private void playThen() {
+ void playThen() {
CommandBuilder play = getNewCommandPlayerToPlay();
if (play != null) {
notifyOnVoiceMessage();
@@ -715,7 +718,7 @@ public class VoiceRouter {
}
}
- private void playAndArriveAtDestination(NextDirectionInfo info) {
+ void playAndArriveAtDestination(NextDirectionInfo info) {
if (isTargetPoint(info)) {
String pointName = info == null ? "" : info.pointName;
CommandBuilder play = getNewCommandPlayerToPlay();
@@ -781,7 +784,7 @@ public class VoiceRouter {
}
}
- private String getTurnType(TurnType t) {
+ String getTurnType(TurnType t) {
if (TurnType.TL == t.getValue()) {
return AbstractPrologCommandPlayer.A_LEFT;
} else if (TurnType.TSHL == t.getValue()) {
diff --git a/OsmAnd/src/net/osmand/plus/voice/AbstractJSCommandPlayer.java b/OsmAnd/src/net/osmand/plus/voice/AbstractJSCommandPlayer.java
new file mode 100644
index 0000000000..84a2e0b04d
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/voice/AbstractJSCommandPlayer.java
@@ -0,0 +1,164 @@
+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 {
+ 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("-"));
+ }
+
+ @Override
+ public String getCurrentVoice() {
+ return null;
+ }
+
+ @Override
+ public CommandBuilder newCommandBuilder() {
+ JSCommandBuilder commandBuilder = new JSCommandBuilder(this);
+ commandBuilder.setParameters("km-m", true);
+ return commandBuilder;
+ }
+
+ @Override
+ public void playCommands(CommandBuilder builder) {
+
+ }
+
+ @Override
+ public void clear() {
+ if(ctx != null && ctx.getSettings() != null) {
+ ctx.getSettings().APPLICATION_MODE.removeListener(this);
+ }
+ abandonAudioFocus();
+ ctx = null;
+ }
+
+ @Override
+ public List execute(List listStruct) {
+ return null;
+ }
+
+ @Override
+ public void updateAudioStream(int streamType) {
+
+ }
+
+ @Override
+ public String getLanguage() {
+ return "en";
+ }
+
+ @Override
+ public boolean supportsStructuredStreetNames() {
+ return true;
+ }
+
+ @Override
+ 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);
+// }
+ }
+}
diff --git a/OsmAnd/src/net/osmand/plus/voice/CommandBuilder.java b/OsmAnd/src/net/osmand/plus/voice/CommandBuilder.java
index 6e17ac0543..01640b1c34 100644
--- a/OsmAnd/src/net/osmand/plus/voice/CommandBuilder.java
+++ b/OsmAnd/src/net/osmand/plus/voice/CommandBuilder.java
@@ -53,8 +53,8 @@ public class CommandBuilder {
/**
*
*/
- private final CommandPlayer commandPlayer;
- private boolean alreadyExecuted = false;
+ protected final CommandPlayer commandPlayer;
+ protected boolean alreadyExecuted = false;
private List listStruct = new ArrayList();
public CommandBuilder(CommandPlayer commandPlayer){
diff --git a/OsmAnd/src/net/osmand/plus/voice/JSCommandBuilder.java b/OsmAnd/src/net/osmand/plus/voice/JSCommandBuilder.java
new file mode 100644
index 0000000000..b49829a548
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/voice/JSCommandBuilder.java
@@ -0,0 +1,235 @@
+package net.osmand.plus.voice;
+
+import net.osmand.PlatformUtil;
+
+import org.apache.commons.logging.Log;
+import org.json.JSONObject;
+import org.mozilla.javascript.Callable;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.NativeJSON;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+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 listStruct = new ArrayList<>();
+ private ScriptableObject jsScope;
+
+ JSCommandBuilder(CommandPlayer commandPlayer) {
+ super(commandPlayer);
+ }
+
+ public void setJSContext(ScriptableObject jsScope) {
+ jsContext = Context.enter();
+ this.jsScope = jsScope;
+ }
+
+ private Object convertStreetName(Map streetName) {
+ return NativeJSON.parse(jsContext, jsScope, new JSONObject(streetName).toString(),
+ new NullCallable());
+ }
+
+ public void setParameters(String metricCons, boolean tts) {
+ 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});
+ }
+
+ 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){
+ Object obj = jsScope.get(name);
+ if (obj instanceof Function) {
+ Function jsFunction = (Function) obj;
+ Object jsResult = jsFunction.call(jsContext, jsScope, jsScope, args);
+ listStruct.add(Context.toString(jsResult));
+ }
+ return this;
+ }
+
+ public JSCommandBuilder goAhead(){
+ return goAhead(-1, new HashMap());
+ }
+
+ public JSCommandBuilder goAhead(double dist, Map streetName){
+ return addCommand(C_GO_AHEAD, dist, convertStreetName(streetName));
+ }
+
+ public JSCommandBuilder makeUTwp(){
+ return makeUT(new HashMap());
+ }
+
+ public JSCommandBuilder makeUT(Map streetName){
+ return makeUT(-1, streetName);
+ }
+ @Override
+ public JSCommandBuilder speedAlarm(int maxSpeed, float speed){
+ return addCommand(C_SPEAD_ALARM, maxSpeed, speed);
+ }
+ @Override
+ public JSCommandBuilder attention(String type){
+ return addCommand(C_ATTENTION, type);
+ }
+ @Override
+ public JSCommandBuilder offRoute(double dist){
+ return addCommand(C_OFF_ROUTE, dist);
+ }
+ @Override
+ public CommandBuilder backOnRoute(){
+ return addCommand(C_BACK_ON_ROUTE);
+ }
+
+ public JSCommandBuilder makeUT(double dist, Map streetName){
+ return addCommand(C_MAKE_UT, dist, convertStreetName(streetName));
+ }
+
+ public JSCommandBuilder prepareMakeUT(double dist, Map streetName){
+ return addCommand(C_PREPARE_MAKE_UT, dist, convertStreetName(streetName));
+ }
+
+
+ public JSCommandBuilder turn(String param, Map streetName) {
+ return turn(param, -1, streetName);
+ }
+
+ public JSCommandBuilder turn(String param, double dist, Map streetName) {
+ return addCommand(C_TURN, param, dist, convertStreetName(streetName));
+ }
+
+ /**
+ *
+ * @param param A_LEFT, A_RIGHT, ...
+ * @param dist
+ * @return
+ */
+ public JSCommandBuilder prepareTurn(String param, double dist, Map streetName){
+ return addCommand(C_PREPARE_TURN, param, dist, convertStreetName(streetName));
+ }
+
+ public JSCommandBuilder prepareRoundAbout(double dist, int exit, Map streetName){
+ return addCommand(C_PREPARE_ROUNDABOUT, dist, exit, convertStreetName(streetName));
+ }
+
+ public JSCommandBuilder roundAbout(double dist, double angle, int exit, Map streetName){
+ return addCommand(C_ROUNDABOUT, dist, angle, exit, convertStreetName(streetName));
+ }
+
+ public JSCommandBuilder roundAbout(double angle, int exit, Map streetName) {
+ return roundAbout(-1, angle, exit, streetName);
+ }
+ @Override
+ public JSCommandBuilder andArriveAtDestination(String name){
+ return addCommand(C_AND_ARRIVE_DESTINATION, name);
+ }
+ @Override
+ public JSCommandBuilder arrivedAtDestination(String name){
+ return addCommand(C_REACHED_DESTINATION, name);
+ }
+ @Override
+ public JSCommandBuilder andArriveAtIntermediatePoint(String name){
+ return addCommand(C_AND_ARRIVE_INTERMEDIATE, name);
+ }
+ @Override
+ public JSCommandBuilder arrivedAtIntermediatePoint(String name) {
+ return addCommand(C_REACHED_INTERMEDIATE, name);
+ }
+ @Override
+ public JSCommandBuilder andArriveAtWayPoint(String name){
+ return addCommand(C_AND_ARRIVE_WAYPOINT, name);
+ }
+ @Override
+ public JSCommandBuilder arrivedAtWayPoint(String name) {
+ return addCommand(C_REACHED_WAYPOINT, name);
+ }
+ @Override
+ public JSCommandBuilder andArriveAtFavorite(String name) {
+ return addCommand(C_AND_ARRIVE_FAVORITE, name);
+ }
+ @Override
+ public JSCommandBuilder arrivedAtFavorite(String name) {
+ return addCommand(C_REACHED_FAVORITE, name);
+ }
+ @Override
+ public JSCommandBuilder andArriveAtPoi(String name) {
+ return addCommand(C_AND_ARRIVE_POI_WAYPOINT, name);
+ }
+ @Override
+ public JSCommandBuilder arrivedAtPoi(String name) {
+ return addCommand(C_REACHED_POI, name);
+ }
+
+ public JSCommandBuilder bearLeft(Map streetName){
+ return addCommand(C_BEAR_LEFT, convertStreetName(streetName));
+ }
+
+ public JSCommandBuilder bearRight(Map streetName){
+ return addCommand(C_BEAR_RIGHT, convertStreetName(streetName));
+ }
+
+ @Override
+ public JSCommandBuilder then(){
+ return addCommand(C_THEN);
+ }
+
+ @Override
+ public JSCommandBuilder gpsLocationLost() {
+ return addCommand(C_LOCATION_LOST);
+ }
+
+ @Override
+ public JSCommandBuilder gpsLocationRecover() {
+ return addCommand(C_LOCATION_RECOVERED);
+ }
+
+ @Override
+ public JSCommandBuilder newRouteCalculated(double dist, int time){
+ return addCommand(C_ROUTE_NEW_CALC, dist, time);
+ }
+
+ @Override
+ public JSCommandBuilder routeRecalculated(double dist, int time){
+ return addCommand(C_ROUTE_RECALC, dist, time);
+ }
+
+ @Override
+ public void play(){
+ this.commandPlayer.playCommands(this);
+ }
+
+ @Override
+ protected List execute(){
+ alreadyExecuted = true;
+ return listStruct;
+ }
+
+ public class NullCallable implements Callable
+ {
+ @Override
+ public Object call(Context context, Scriptable scope, Scriptable holdable, Object[] objects)
+ {
+ return objects[1];
+ }
+ }
+}
diff --git a/OsmAnd/src/net/osmand/plus/voice/JSMediaCommandPlayerImpl.java b/OsmAnd/src/net/osmand/plus/voice/JSMediaCommandPlayerImpl.java
new file mode 100644
index 0000000000..b1b0b28dfc
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/voice/JSMediaCommandPlayerImpl.java
@@ -0,0 +1,101 @@
+package net.osmand.plus.voice;
+
+import android.media.MediaPlayer;
+import android.system.Os;
+
+import net.osmand.IndexConstants;
+import net.osmand.PlatformUtil;
+import net.osmand.plus.ApplicationMode;
+import net.osmand.plus.OsmandApplication;
+import net.osmand.plus.routing.VoiceRouter;
+
+import org.apache.commons.logging.Log;
+import org.mozilla.javascript.ScriptableObject;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import static net.osmand.plus.voice.AbstractPrologCommandPlayer.DELAY_CONST;
+
+public class JSMediaCommandPlayerImpl extends MediaCommandPlayerImpl {
+
+ private static final org.apache.commons.logging.Log log = PlatformUtil.getLog(JSMediaCommandPlayerImpl.class);
+
+ private ScriptableObject jsScope;
+ private OsmandApplication app;
+
+ public JSMediaCommandPlayerImpl(OsmandApplication ctx, ApplicationMode applicationMode, VoiceRouter vrt, String voiceProvider) throws CommandPlayerException {
+ super(ctx, applicationMode, vrt, voiceProvider);
+ app = ctx;
+
+ org.mozilla.javascript.Context context = org.mozilla.javascript.Context.enter();
+ context.setOptimizationLevel(-1);
+ jsScope = context.initSafeStandardObjects();
+ try {
+ BufferedReader br = new BufferedReader(new FileReader(new File(
+ app.getAppPath(IndexConstants.VOICE_INDEX_DIR).getAbsolutePath() +
+ "/" + voiceProvider + "/" + language + "_tts.js")));
+ context.evaluateReader(jsScope, br, "JS", 1, null);
+ br.close();
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ } finally {
+ org.mozilla.javascript.Context.exit();
+ }
+ }
+
+ @Override
+ public synchronized void playCommands(CommandBuilder builder) {
+ if(vrt.isMute()) {
+ return;
+ }
+ filesToPlay.addAll(splitAnnouncements(builder.execute()));
+
+ // If we have not already started to play audio, start.
+ if (mediaPlayer == null) {
+ requestAudioFocus();
+ // Delay first prompt of each batch to allow BT SCO connection being established
+ if (ctx != null && ctx.getSettings().AUDIO_STREAM_GUIDANCE.getModeValue(getApplicationMode()) == 0) {
+ try {
+ log.debug("Delaying MediaCommandPlayer for BT SCO");
+ Thread.sleep(ctx.getSettings().BT_SCO_DELAY.get());
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ playQueue();
+ }
+
+ private List splitAnnouncements(List execute) {
+ List result = new ArrayList<>();
+ for (String files : execute) {
+ result.addAll(Arrays.asList(files.split(" ")));
+ }
+ return result;
+ }
+
+ @Override
+ public JSCommandBuilder newCommandBuilder() {
+ JSCommandBuilder commandBuilder = new JSCommandBuilder(this);
+ commandBuilder.setJSContext(jsScope);
+ commandBuilder.setParameters(app.getSettings().METRIC_SYSTEM.get().toTTSString(), false);
+ return commandBuilder;
+ }
+
+ public static boolean isMyData(File voiceDir) {
+ for (File f : voiceDir.listFiles()) {
+ if (f.getName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java b/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java
new file mode 100644
index 0000000000..da4ee6e644
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java
@@ -0,0 +1,348 @@
+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;
+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.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+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 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;
+
+ private HashMap params = new HashMap();
+
+ private static int ttsRequests = 0;
+
+ 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;
+ 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());
+ org.mozilla.javascript.Context context = org.mozilla.javascript.Context.enter();
+ context.setOptimizationLevel(-1);
+ jsScope = context.initSafeStandardObjects();
+ try {
+ BufferedReader br = new BufferedReader(new FileReader(new File(
+ app.getAppPath(IndexConstants.VOICE_INDEX_DIR).getAbsolutePath() +
+ "/" + voiceProvider + "/" + language + "_tts.js")));
+ context.evaluateReader(jsScope, br, "JS", 1, null);
+ br.close();
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ } finally {
+ org.mozilla.javascript.Context.exit();
+ }
+ }
+
+ 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
+ public String getCurrentVoice() {
+ return null;
+ }
+
+ @Override
+ public JSCommandBuilder newCommandBuilder() {
+ JSCommandBuilder commandBuilder = new JSCommandBuilder(this);
+ commandBuilder.setJSContext(jsScope);
+ commandBuilder.setParameters(app.getSettings().METRIC_SYSTEM.get().toTTSString(), true);
+ return commandBuilder;
+ }
+
+ @Override
+ public void playCommands(CommandBuilder builder) {
+ final List execute = builder.execute(); //list of strings, the speech text, play it
+ StringBuilder bld = new StringBuilder();
+ for (String s : execute) {
+ bld.append(s).append(' ');
+ }
+ 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) {
+ ttsRequests++;
+ if (android.os.Build.VERSION.SDK_INT < 21) {
+ params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,""+System.currentTimeMillis());
+ mTts.playSilence(app.getSettings().BT_SCO_DELAY.get(), TextToSpeech.QUEUE_ADD, params);
+ } else {
+ mTts.playSilentUtterance(app.getSettings().BT_SCO_DELAY.get(), TextToSpeech.QUEUE_ADD, ""+System.currentTimeMillis());
+ }
+ }
+ }
+ Log.d(TAG, "ttsRequests= "+ttsRequests);
+ params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,""+System.currentTimeMillis());
+ mTts.speak(bld.toString(), TextToSpeech.QUEUE_ADD, params);
+ // Audio focus will be released when onUtteranceCompleted() completed is called by the TTS engine.
+ } else if (app != null && vrt.isMute()) {
+ // sendAlertToAndroidWear(ctx, bld.toString());
+ }
+ }
+
+ @Override
+ 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 language;
+ }
+
+ @Override
+ public boolean supportsStructuredStreetNames() {
+ return true;
+ }
+
+
+ private void internalClear() {
+ ttsRequests = 0;
+ speechAllowed = false;
+ if (mTts != null) {
+ mTts.shutdown();
+ mTts = null;
+ }
+ abandonAudioFocus();
+ mTtsContext = null;
+ ttsVoiceStatus = "";
+ ttsVoiceUsed = "";
+ }
+
+ public static boolean isMyData(File voiceDir) {
+ boolean isTTS = voiceDir.getName().contains("tts");
+ if (!isTTS) {
+ return false;
+ }
+ for (File f : voiceDir.listFiles()) {
+ if (f.getName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ 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;
+ }
+}
diff --git a/OsmAnd/src/net/osmand/plus/voice/MediaCommandPlayerImpl.java b/OsmAnd/src/net/osmand/plus/voice/MediaCommandPlayerImpl.java
index a48a904d98..a3366dec37 100644
--- a/OsmAnd/src/net/osmand/plus/voice/MediaCommandPlayerImpl.java
+++ b/OsmAnd/src/net/osmand/plus/voice/MediaCommandPlayerImpl.java
@@ -29,10 +29,10 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen
private static final Log log = PlatformUtil.getLog(MediaCommandPlayerImpl.class);
// playing media
- private MediaPlayer mediaPlayer;
+ MediaPlayer mediaPlayer;
// indicates that player is ready to play first file
- private List filesToPlay = Collections.synchronizedList(new ArrayList());
- private VoiceRouter vrt;
+ List filesToPlay = Collections.synchronizedList(new ArrayList());
+ VoiceRouter vrt;
public MediaCommandPlayerImpl(OsmandApplication ctx, ApplicationMode applicationMode, VoiceRouter vrt, String voiceProvider)
@@ -95,7 +95,7 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen
playQueue();
}
- private synchronized void playQueue() {
+ synchronized void playQueue() {
if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer();
}
@@ -159,7 +159,7 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen
* @param file
*/
private void playFile(File file) {
- if (!file.exists()) {
+ if (!file.exists() || file.isDirectory()) {
log.error("Unable to play, does not exist: "+file);
playQueue();
return;