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;