From a2ec75c7fd48aeb7e75c69bc7839b6a74be6f7b0 Mon Sep 17 00:00:00 2001 From: Alexander Marchuk Date: Thu, 22 Oct 2015 20:34:27 +0300 Subject: [PATCH 1/8] [android] fix: Strings. --- strings.txt | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/strings.txt b/strings.txt index 020a0db1aa..a3855bd2b1 100644 --- a/strings.txt +++ b/strings.txt @@ -5224,6 +5224,30 @@ en = Modern light ru = Современная светлая + [pref_tts_enable_title] + tags = android,ios + comment = Settings «Route» category: «Tts enabled» title + en = Voice instructions + ru = Голосовые инструкции + + [pref_tts_enable_summary] + tags = android,ios + comment = Settings «Route» category: «Tts enabled» summary + en = Turn instructions + ru = Подсказки о поворотах и прочих манёврах + + [pref_tts_language_title] + tags = android,ios + comment = Settings «Route» category: «Tts language» title + en = Voice language + ru = Язык подсказок + + [pref_tts_unavailable] + tags = android,ios + comment = Settings «Route» category: «Tts unavailable» subtitle + en = TTS unavailable + ru = Голосовые подсказки недоступны + [placepage_distance] en = Distance fr = Distance From ec1de77a33c1027492fc641a6183d1dc2782ecb3 Mon Sep 17 00:00:00 2001 From: Alexander Marchuk Date: Thu, 22 Oct 2015 20:37:09 +0300 Subject: [PATCH 2/8] [android] fix: Generated strings. --- android/res/values-ru/strings.xml | 8 ++++++++ android/res/values/strings.xml | 8 ++++++++ iphone/Maps/en.lproj/Localizable.strings | 12 ++++++++++++ iphone/Maps/ru.lproj/Localizable.strings | 12 ++++++++++++ 4 files changed, 40 insertions(+) diff --git a/android/res/values-ru/strings.xml b/android/res/values-ru/strings.xml index 34e0b107b1..210c47c15e 100644 --- a/android/res/values-ru/strings.xml +++ b/android/res/values-ru/strings.xml @@ -327,6 +327,14 @@ Классическая тёмная Современная светлая + + Голосовые инструкции + + Подсказки о поворотах и прочих манёврах + + Язык подсказок + + Голосовые подсказки недоступны Расстояние Координаты Без категории diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml index 3a54e2681d..e38124bd04 100644 --- a/android/res/values/strings.xml +++ b/android/res/values/strings.xml @@ -329,6 +329,14 @@ Classic dark Modern light + + Voice instructions + + Turn instructions + + Voice language + + TTS unavailable Distance Coordinates Unsorted diff --git a/iphone/Maps/en.lproj/Localizable.strings b/iphone/Maps/en.lproj/Localizable.strings index 77f0d019dd..f0da92bf46 100644 --- a/iphone/Maps/en.lproj/Localizable.strings +++ b/iphone/Maps/en.lproj/Localizable.strings @@ -508,6 +508,18 @@ /* «Map style» entry value */ "pref_map_style_modern_light" = "Modern light"; +/* Settings «Route» category: «Tts enabled» title */ +"pref_tts_enable_title" = "Voice instructions"; + +/* Settings «Route» category: «Tts enabled» summary */ +"pref_tts_enable_summary" = "Turn instructions"; + +/* Settings «Route» category: «Tts language» title */ +"pref_tts_language_title" = "Voice language"; + +/* Settings «Route» category: «Tts unavailable» subtitle */ +"pref_tts_unavailable" = "TTS unavailable"; + "placepage_distance" = "Distance"; "placepage_coordinates" = "Coordinates"; diff --git a/iphone/Maps/ru.lproj/Localizable.strings b/iphone/Maps/ru.lproj/Localizable.strings index 2dd19f3545..b5557fc611 100644 --- a/iphone/Maps/ru.lproj/Localizable.strings +++ b/iphone/Maps/ru.lproj/Localizable.strings @@ -508,6 +508,18 @@ /* «Map style» entry value */ "pref_map_style_modern_light" = "Современная светлая"; +/* Settings «Route» category: «Tts enabled» title */ +"pref_tts_enable_title" = "Голосовые инструкции"; + +/* Settings «Route» category: «Tts enabled» summary */ +"pref_tts_enable_summary" = "Подсказки о поворотах и прочих манёврах"; + +/* Settings «Route» category: «Tts language» title */ +"pref_tts_language_title" = "Язык подсказок"; + +/* Settings «Route» category: «Tts unavailable» subtitle */ +"pref_tts_unavailable" = "Голосовые подсказки недоступны"; + "placepage_distance" = "Расстояние"; "placepage_coordinates" = "Координаты"; From b194d3d271bbd5b1894390abe19d51d3d7e7cd01 Mon Sep 17 00:00:00 2001 From: Alexander Marchuk Date: Thu, 22 Oct 2015 20:39:23 +0300 Subject: [PATCH 3/8] [android] refactor: TTS refactored. --- android/AndroidManifest.xml | 3 +- android/jni/com/mapswithme/maps/sound/tts.cpp | 4 +- android/res/values/strings-tts.xml | 80 +++++++ .../src/com/mapswithme/maps/MwmActivity.java | 3 +- .../com/mapswithme/maps/MwmApplication.java | 29 +-- .../maps/search/SearchFragment.java | 18 +- .../maps/settings/MapPrefsFragment.java | 2 +- .../mapswithme/maps/sound/LanguageData.java | 71 ++++++ .../com/mapswithme/maps/sound/TtsPlayer.java | 203 ++++++++++-------- android/src/com/mapswithme/util/Config.java | 36 +++- android/src/com/mapswithme/util/Language.java | 41 ---- .../{in.json => id.json}/localize.json | 0 12 files changed, 325 insertions(+), 165 deletions(-) create mode 100644 android/res/values/strings-tts.xml create mode 100644 android/src/com/mapswithme/maps/sound/LanguageData.java rename data/sound-strings/{in.json => id.json}/localize.json (100%) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 7f012cb66f..d397d503c7 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -171,8 +171,7 @@ android:name="com.mapswithme.maps.MwmActivity" android:launchMode="singleTask" android:theme="@style/MwmTheme.MainActivity" - android:windowSoftInputMode="stateAlwaysHidden|adjustPan"> - + android:windowSoftInputMode="stateAlwaysHidden|adjustPan"/> EnableTurnNotifications(enable == JNI_TRUE ? true : false); + return frm()->EnableTurnNotifications(static_cast(enable)); } JNIEXPORT jboolean JNICALL Java_com_mapswithme_maps_sound_TtsPlayer_nativeAreTurnNotificationsEnabled(JNIEnv * env, jclass clazz) { - return frm()->AreTurnNotificationsEnabled() ? JNI_TRUE : JNI_FALSE; + return static_cast(frm()->AreTurnNotificationsEnabled()); } JNIEXPORT void JNICALL diff --git a/android/res/values/strings-tts.xml b/android/res/values/strings-tts.xml new file mode 100644 index 0000000000..e96013793a --- /dev/null +++ b/android/res/values/strings-tts.xml @@ -0,0 +1,80 @@ + + + + + en + ru + pl + sv + tr + fr + nl + de + ar + el + it + cs + hu + ro + ja + da + es + fi + hi + hr + id + ko + pt + sk + sw + th + zh-tw:zh-Hant + zh-cn:zh-Hans + + + + English + Русский + Polski + Svenska + Türkçe + Français + Nederlands + Deutsch + العربية + Ελληνικά + Italiano + Čeština + Magyar + Română + 日本語 + Dansk + Español + Suomi + हिन्दी + Hrvatski + TODO: Indonesian + 한국어 + Português + Slovenčina + Kiswahili + ไทย + TODO: Chinese Traditional + TODO: Chinese Simplified + + diff --git a/android/src/com/mapswithme/maps/MwmActivity.java b/android/src/com/mapswithme/maps/MwmActivity.java index 53b9cbeebd..1e886890bc 100644 --- a/android/src/com/mapswithme/maps/MwmActivity.java +++ b/android/src/com/mapswithme/maps/MwmActivity.java @@ -767,14 +767,13 @@ public class MwmActivity extends BaseMwmFragmentActivity { super.onStart(); - TtsPlayer.INSTANCE.reinitIfLocaleChanged(); if (!mIsFragmentContainer) popFragment(); } private void adjustZoomButtons(boolean routingActive) { - boolean show = (routingActive || Config.getShowZoomButtons()); + boolean show = (routingActive || Config.showZoomButtons()); UiUtils.showIf(show, mBtnZoomIn, mBtnZoomOut); if (!show) diff --git a/android/src/com/mapswithme/maps/MwmApplication.java b/android/src/com/mapswithme/maps/MwmApplication.java index 52dfc07e8a..7287cc9d11 100644 --- a/android/src/com/mapswithme/maps/MwmApplication.java +++ b/android/src/com/mapswithme/maps/MwmApplication.java @@ -1,16 +1,17 @@ package com.mapswithme.maps; +import android.app.Application; import android.content.SharedPreferences; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Environment; import android.preference.PreferenceManager; import android.util.Log; - import com.google.gson.Gson; import com.mapswithme.country.ActiveCountryTree; import com.mapswithme.country.CountryItem; import com.mapswithme.maps.background.Notifier; import com.mapswithme.maps.bookmarks.data.BookmarkManager; +import com.mapswithme.maps.sound.TtsPlayer; import com.mapswithme.util.Config; import com.mapswithme.util.Constants; import com.mapswithme.util.UiUtils; @@ -24,7 +25,8 @@ import com.parse.SaveCallback; import java.io.File; -public class MwmApplication extends android.app.Application implements ActiveCountryTree.ActiveCountryListener +public class MwmApplication extends Application + implements ActiveCountryTree.ActiveCountryListener { private final static String TAG = "MwmApplication"; @@ -32,32 +34,32 @@ public class MwmApplication extends android.app.Application implements ActiveCou private static final String PREF_PARSE_DEVICE_TOKEN = "ParseDeviceToken"; private static final String PREF_PARSE_INSTALLATION_ID = "ParseInstallationId"; - private static MwmApplication mSelf; + private static MwmApplication sSelf; + private static SharedPreferences sPrefs; private final Gson mGson = new Gson(); - private static SharedPreferences mPrefs; - private boolean mAreCountersInitialised; + private boolean mAreCountersInitialized; private boolean mIsFrameworkInitialized; public MwmApplication() { super(); - mSelf = this; + sSelf = this; } public static MwmApplication get() { - return mSelf; + return sSelf; } public static Gson gson() { - return mSelf.mGson; + return sSelf.mGson; } public static SharedPreferences prefs() { - return mPrefs; + return sPrefs; } @Override @@ -92,10 +94,10 @@ public class MwmApplication extends android.app.Application implements ActiveCou super.onCreate(); initParse(); - mPrefs = getSharedPreferences(getString(R.string.pref_file_name), MODE_PRIVATE); + sPrefs = getSharedPreferences(getString(R.string.pref_file_name), MODE_PRIVATE); } - public synchronized void initNativeCore() + public void initNativeCore() { if (mIsFrameworkInitialized) return; @@ -107,6 +109,7 @@ public class MwmApplication extends android.app.Application implements ActiveCou ActiveCountryTree.addListener(this); initNativeStrings(); BookmarkManager.getIcons(); // init BookmarkManager (automatically loads bookmarks) + TtsPlayer.INSTANCE.init(this); mIsFrameworkInitialized = true; } @@ -226,9 +229,9 @@ public class MwmApplication extends android.app.Application implements ActiveCou public void initCounters() { - if (!mAreCountersInitialised) + if (!mAreCountersInitialized) { - mAreCountersInitialised = true; + mAreCountersInitialized = true; Config.updateLaunchCounter(); PreferenceManager.setDefaultValues(this, R.xml.prefs_misc, false); } diff --git a/android/src/com/mapswithme/maps/search/SearchFragment.java b/android/src/com/mapswithme/maps/search/SearchFragment.java index 9ae98d5214..0c4f16cd51 100644 --- a/android/src/com/mapswithme/maps/search/SearchFragment.java +++ b/android/src/com/mapswithme/maps/search/SearchFragment.java @@ -15,7 +15,6 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.mapswithme.country.ActiveCountryTree; import com.mapswithme.country.CountrySuggestFragment; import com.mapswithme.maps.Framework; @@ -24,7 +23,6 @@ import com.mapswithme.maps.R; import com.mapswithme.maps.base.BaseMwmFragment; import com.mapswithme.maps.base.OnBackPressListener; import com.mapswithme.maps.location.LocationHelper; -import com.mapswithme.maps.sound.TtsPlayer; import com.mapswithme.maps.widget.SearchToolbarController; import com.mapswithme.util.InputUtils; import com.mapswithme.util.Language; @@ -82,7 +80,7 @@ public class SearchFragment extends BaseMwmFragment } // TODO: This code only for demonstration purposes and will be removed soon - if (trySwitchOnTurnSound(query) || tryChangeMapStyle(query)) + if (tryChangeMapStyle(query)) return; runSearch(); @@ -333,20 +331,6 @@ public class SearchFragment extends BaseMwmFragment return true; } - - private boolean trySwitchOnTurnSound(String query) - { - final boolean sound = "?sound".equals(query); - final boolean nosound = "?nosound".equals(query); - - if (!sound && !nosound) - return false; - - hideSearch(); - TtsPlayer.INSTANCE.enable(sound); - - return sound; - } // FIXME END protected void showSingleResultOnMap(int resultIndex) diff --git a/android/src/com/mapswithme/maps/settings/MapPrefsFragment.java b/android/src/com/mapswithme/maps/settings/MapPrefsFragment.java index f2211f8bd3..83d7dc7682 100644 --- a/android/src/com/mapswithme/maps/settings/MapPrefsFragment.java +++ b/android/src/com/mapswithme/maps/settings/MapPrefsFragment.java @@ -87,7 +87,7 @@ public class MapPrefsFragment extends BaseXmlSettingsFragment }); pref = findPreference(getString(R.string.pref_show_zoom_buttons)); - ((SwitchPreference)pref).setChecked(Config.getShowZoomButtons()); + ((SwitchPreference)pref).setChecked(Config.showZoomButtons()); pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override diff --git a/android/src/com/mapswithme/maps/sound/LanguageData.java b/android/src/com/mapswithme/maps/sound/LanguageData.java new file mode 100644 index 0000000000..96cf084a10 --- /dev/null +++ b/android/src/com/mapswithme/maps/sound/LanguageData.java @@ -0,0 +1,71 @@ +package com.mapswithme.maps.sound; + +import java.util.Locale; + +public class LanguageData +{ + public final Locale locale; + public final String name; + public final String internalCode; + + private int mStatus; + + private LanguageData(String name, Locale locale, String internalCode) + { + this.locale = locale; + this.name = name; + this.internalCode = internalCode; + } + + static LanguageData parse(String line, String name) + { + String[] parts = line.split(":"); + String internalCode = (parts.length > 1 ? parts[1] : null); + + parts = parts[0].split("-"); + String language = parts[0]; + String country = (parts.length > 1 ? parts[1] : ""); + Locale locale = new Locale(language, country.toUpperCase()); + + return new LanguageData(name, locale, (internalCode == null ? language : internalCode)); + } + + void setStatus(int status) + { + mStatus = status; + } + + public int getStatus() + { + return mStatus; + } + + public boolean matchesLocale(Locale locale) + { + String lang = locale.getLanguage(); + if (!lang.equals(this.locale.getLanguage())) + return false; + + if ("zh".equals(lang) && "zh-Hant".equals(internalCode)) + { + // Chinese is a special case + String country = locale.getCountry(); + return "TW".equals(country) || + "MO".equals(country) || + "HK".equals(country); + } + + return true; + } + + public boolean matchesInternalCode(String internalCode) + { + return this.internalCode.equals(internalCode); + } + + @Override + public String toString() + { + return "(" + mStatus + ") " + name + ": " + locale + ", internal: " + internalCode; + } +} diff --git a/android/src/com/mapswithme/maps/sound/TtsPlayer.java b/android/src/com/mapswithme/maps/sound/TtsPlayer.java index e9d1fceb1d..d154e8b27a 100644 --- a/android/src/com/mapswithme/maps/sound/TtsPlayer.java +++ b/android/src/com/mapswithme/maps/sound/TtsPlayer.java @@ -1,14 +1,18 @@ package com.mapswithme.maps.sound; +import android.content.Context; +import android.content.res.Resources; import android.speech.tts.TextToSpeech; +import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; -import android.widget.Toast; - import com.mapswithme.maps.Framework; import com.mapswithme.maps.MwmApplication; -import com.mapswithme.util.Language; +import com.mapswithme.maps.R; +import com.mapswithme.util.Config; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; @@ -16,143 +20,148 @@ public enum TtsPlayer { INSTANCE; + private static final String TAG = "TtsPlayer"; private static final Locale DEFAULT_LOCALE = Locale.US; private static final float SPEECH_RATE = 1.2f; - // The both mTtts and mTtsLocale should be initialized before usage. private TextToSpeech mTts; - private Locale mTtsLocale; - private boolean mIsLocaleChanging; + private boolean mInitializing; - private final static String TAG = "TtsPlayer"; + // TTS is locked down due to lack of supported languages + private boolean mUnavailable; TtsPlayer() {} - public void reinitIfLocaleChanged() + private @Nullable LanguageData findSupportedLanguage(String internalCode, List langs) { - if (!isValid()) - return; // TtsPlayer was not inited yet. + if (TextUtils.isEmpty(internalCode)) + return null; - final Locale locale = getDefaultLocale(); - if (!isLocaleEqual(locale)) - initTts(locale); + for (LanguageData lang : langs) + if (lang.matchesInternalCode(internalCode)) + return lang; + + return null; } - private Locale getDefaultLocale() + private @Nullable LanguageData findSupportedLanguage(Locale locale, List langs) { - final Locale locale = Locale.getDefault(); - return locale == null ? DEFAULT_LOCALE : locale; + if (locale == null) + return null; + + for (LanguageData lang : langs) + if (lang.matchesLocale(locale)) + return lang; + + return null; } - private boolean isLocaleEqual(Locale locale) + private void setLanguageInternal(LanguageData lang) { - return locale.getLanguage().equals(mTtsLocale.getLanguage()) && - locale.getCountry().equals(mTtsLocale.getCountry()); + mTts.setLanguage(lang.locale); + nativeSetTurnNotificationsLocale(lang.internalCode); + Config.setTtsLanguage(lang.internalCode); } - private boolean isLocaleAvailable(Locale locale) + public void setLanguage(LanguageData lang) { - final int avail = mTts.isLanguageAvailable(locale); - return avail == TextToSpeech.LANG_AVAILABLE || avail == TextToSpeech.LANG_COUNTRY_AVAILABLE || - avail == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE; + if (lang != null) + setLanguageInternal(lang); } - private void initTts(final Locale locale) + private LanguageData getDefaultLanguage(List langs) { - if (mIsLocaleChanging) - return; // Preventing reiniting while creating TextToSpeech object. There's a small possibility a new locale is skipped. + Locale defLocale = Locale.getDefault(); + if (defLocale == null) + defLocale = DEFAULT_LOCALE; - if (mTts != null && mTtsLocale != null && mTtsLocale.equals(locale)) - return; - - mTtsLocale = null; - mIsLocaleChanging = true; - - if (mTts != null) + LanguageData lang = findSupportedLanguage(defLocale, langs); + if (lang == null) { - mTts.stop(); - mTts.shutdown(); + // No supported languages found, lock down TTS :( + mUnavailable = true; + Config.setTtsEnabled(false); } - mTts = new TextToSpeech(MwmApplication.get(), new TextToSpeech.OnInitListener() + return lang; + } + + public LanguageData getSelectedLanguage(List langs) + { + return findSupportedLanguage(Config.getTtsLanguage(), langs); + } + + public void init(Context context) + { + if (mTts != null || mInitializing || mUnavailable) + return; + + mInitializing = true; + mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() { @Override public void onInit(int status) { - // This method is called asynchronously. - mIsLocaleChanging = false; if (status == TextToSpeech.ERROR) { - Log.w(TAG, "Can't initialize TextToSpeech for locale " + locale.getLanguage() + " " + locale.getCountry()); + Log.e(TAG, "Failed to initialize TextToSpeach"); + mUnavailable = true; + mInitializing = false; return; } - if (isLocaleAvailable(locale)) - { - Log.i(TAG, "The locale " + locale.getLanguage() + " " + locale.getCountry() + " will be used for TTS."); - mTtsLocale = locale; - } - else if (isLocaleAvailable(DEFAULT_LOCALE)) - { - Log.w(TAG, "TTS is not available for locale " + locale.getLanguage() + " " + locale.getCountry() + - ". The default locale " + DEFAULT_LOCALE.getLanguage() + " " + DEFAULT_LOCALE.getCountry() + " will be used."); - mTtsLocale = DEFAULT_LOCALE; - } - else - { - Log.w(TAG, "TTS is not available for locale " + locale.getLanguage() + " " + locale.getCountry() + - " and for the default locale " + DEFAULT_LOCALE.getLanguage() + " " + DEFAULT_LOCALE.getCountry() + - ". TTS will be switched off."); - mTtsLocale = null; - return; - } + List langs = getAvailableLanguages(false); + LanguageData defLang = getDefaultLanguage(langs); + LanguageData lang = getSelectedLanguage(langs); + if (lang == null) + lang = defLang; - String localeTwine = Language.localeToTwineLanguage(mTtsLocale); - if (TextUtils.isEmpty(localeTwine)) + if (lang != null) { - Log.w(TAG, "Cann't get a twine language name for the locale " + locale.getLanguage() + " " + locale.getCountry() + - ". TTS will be switched off."); - mTtsLocale = null; - return; + String curLangCode = Config.getTtsLanguage(); + if (defLang != null && !defLang.matchesInternalCode(curLangCode)) + { + // Selected TTS locale does not match current defaults. And it was NOT set by user. + // Assume that the current locale was equal to old default one. + // So, let the new default locale be current. + if (!Config.isTtsLanguageSetByUser()) + lang = findSupportedLanguage(defLang.internalCode, langs); + } + + setLanguage(lang); } mTts.setSpeechRate(SPEECH_RATE); - nativeSetTurnNotificationsLocale(localeTwine); - mTts.setLanguage(mTtsLocale); - Log.i(TAG, "setLocaleIfAvailable() onInit nativeSetTurnNotificationsLocale(" + localeTwine + ")"); + setEnabled(Config.isTtsEnabled()); + mInitializing = false; } }); } - private boolean isValid() + public boolean isReady() { - return !mIsLocaleChanging && mTts != null && mTtsLocale != null; + return (mTts != null && !mUnavailable && !mInitializing); } private void speak(String textToSpeak) { - // @TODO(vbykoianko) removes these two toasts below when the test period is finished. - Toast.makeText(MwmApplication.get(), textToSpeak, Toast.LENGTH_SHORT).show(); - if (mTts.speak(textToSpeak, TextToSpeech.QUEUE_ADD, null) == TextToSpeech.ERROR) - { - Log.e(TAG, "TextToSpeech returns TextToSpeech.ERROR."); - Toast.makeText(MwmApplication.get(), "TTS error", Toast.LENGTH_SHORT).show(); - } + //noinspection deprecation + mTts.speak(textToSpeak, TextToSpeech.QUEUE_ADD, null); } public void playTurnNotifications() { // It's necessary to call Framework.nativeGenerateTurnNotifications() even if TtsPlayer is invalid. final String[] turnNotifications = Framework.nativeGenerateTurnNotifications(); - - if (turnNotifications != null && isValid()) + + if (turnNotifications != null && isReady()) for (String textToSpeak : turnNotifications) speak(textToSpeak); } public void stop() { - if(mTts != null) + if (isReady()) mTts.stop(); } @@ -161,18 +170,40 @@ public enum TtsPlayer return nativeAreTurnNotificationsEnabled(); } - // Note. After a call of enable(true) the flag enabled in cpp core will be set. - // But later in onInit callback the initialization could fail. - // In that case isValid() returns false and every call of playTurnNotifications returns in the beginning. - public void enable(boolean enabled) + public void setEnabled(boolean enabled) { - if (enabled && !isValid()) - initTts(getDefaultLocale()); nativeEnableTurnNotifications(enabled); } + public List getAvailableLanguages(boolean includeDownloadable) + { + List res = new ArrayList<>(); + if (mUnavailable || mTts == null) + return res; + + Resources resources = MwmApplication.get().getResources(); + String[] codes = resources.getStringArray(R.array.tts_languages_supported); + String[] names = resources.getStringArray(R.array.tts_language_names); + + for (int i = 0; i < codes.length; i++) + { + LanguageData lang = LanguageData.parse(codes[i], names[i]); + int status = mTts.isLanguageAvailable(lang.locale); + + int requiredStatus = (includeDownloadable ? TextToSpeech.LANG_MISSING_DATA + : TextToSpeech.LANG_AVAILABLE); + if (status >= requiredStatus) + { + lang.setStatus(status); + res.add(lang); + } + } + + return res; + } + private native static void nativeEnableTurnNotifications(boolean enable); private native static boolean nativeAreTurnNotificationsEnabled(); - private native static void nativeSetTurnNotificationsLocale(String locale); + private native static void nativeSetTurnNotificationsLocale(String code); private native static String nativeGetTurnNotificationsLocale(); } diff --git a/android/src/com/mapswithme/util/Config.java b/android/src/com/mapswithme/util/Config.java index e8faa5bfdf..c48c7b0639 100644 --- a/android/src/com/mapswithme/util/Config.java +++ b/android/src/com/mapswithme/util/Config.java @@ -13,6 +13,10 @@ public final class Config private static final String KEY_APP_LAST_SESSION_TIMESTAMP = "LastSessionTimestamp"; private static final String KEY_APP_FIRST_INSTALL_FLAVOR = "FirstInstallFlavor"; + private static final String KEY_TTS_ENABLED = "TtsEnabled"; + private static final String KEY_TTS_LANGUAGE = "TtsLanguage"; + private static final String KEY_TTS_LANGUAGE_SET_BY_USER = "TtsLanguageSetByUser"; + private static final String KEY_PREF_ZOOM_BUTTONS = "ZoomButtonsEnabled"; private static final String KEY_PREF_STATISTICS = "StatisticsEnabled"; @@ -167,7 +171,37 @@ public final class Config incrementSessionNumber(); } - public static boolean getShowZoomButtons() + public static boolean isTtsEnabled() + { + return getBool(KEY_TTS_ENABLED, true); + } + + public static void setTtsEnabled(boolean enabled) + { + setBool(KEY_TTS_ENABLED, enabled); + } + + public static boolean isTtsLanguageSetByUser() + { + return getBool(KEY_TTS_LANGUAGE_SET_BY_USER); + } + + public static void setTtsLanguageSetByUser() + { + setBool(KEY_TTS_LANGUAGE_SET_BY_USER, true); + } + + public static String getTtsLanguage() + { + return getString(KEY_TTS_LANGUAGE); + } + + public static void setTtsLanguage(String language) + { + setString(KEY_TTS_LANGUAGE, language); + } + + public static boolean showZoomButtons() { return getBool(KEY_PREF_ZOOM_BUTTONS, true); } diff --git a/android/src/com/mapswithme/util/Language.java b/android/src/com/mapswithme/util/Language.java index cc1ed97527..151ae76103 100644 --- a/android/src/com/mapswithme/util/Language.java +++ b/android/src/com/mapswithme/util/Language.java @@ -1,18 +1,14 @@ package com.mapswithme.util; import android.content.Context; -import android.text.TextUtils; -import android.util.Log; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; - import com.mapswithme.maps.MwmApplication; import java.util.Locale; public class Language { - private final static String TAG = "Language"; // Locale.getLanguage() returns even 3-letter codes, not that we need in the C++ core, // so we use locale itself, like zh_CN public static String getDefaultLocale() @@ -34,41 +30,4 @@ public class Language return getDefaultLocale(); } - - // Converts Locale to twine language name. - // If locale can be converted returns a twine language name. For example zh-Hans, ru, en and so on. - // If not returns an empty string. - public static String localeToTwineLanguage(Locale locale) - { - if (locale == null) - { - Log.e(TAG, "localeToTwineLanguage was called with null Locale."); - return ""; - } - - final String chinese = Locale.CHINESE.getLanguage(); - final String language = locale.getLanguage(); - final String country = locale.getCountry(); - - if (chinese == null || language == null || country == null) - { - Log.e(TAG, "Methods Locale.getLanguage or Locale.getCountry return null."); - return ""; - } - - if (chinese.equals(language)) - { - if (country.equals("TW") || country.equals("MO") || country.equals("HK")) - { - return "zh-Hant"; // Chinese traditional - } - return "zh-Hans"; // Chinese simplified - } - if (TextUtils.isEmpty(language)) - { - Log.e(TAG, "locale.getLanguage() returns null or empty string."); - return ""; - } - return language; - } } diff --git a/data/sound-strings/in.json/localize.json b/data/sound-strings/id.json/localize.json similarity index 100% rename from data/sound-strings/in.json/localize.json rename to data/sound-strings/id.json/localize.json From 4e48470180a436c4970726c9d40a33f998207a93 Mon Sep 17 00:00:00 2001 From: Alexander Marchuk Date: Fri, 16 Oct 2015 19:06:57 +0300 Subject: [PATCH 4/8] [android] add: TTS settings. --- android/res/values/donottranslate.xml | 2 + android/res/xml/prefs_headers.xml | 4 + android/res/xml/prefs_route.xml | 9 ++ .../maps/settings/RoutePrefsFragment.java | 131 ++++++++++++++++++ .../maps/settings/SettingsActivity.java | 2 + .../util/statistics/Statistics.java | 1 + 6 files changed, 149 insertions(+) create mode 100644 android/res/xml/prefs_route.xml create mode 100644 android/src/com/mapswithme/maps/settings/RoutePrefsFragment.java diff --git a/android/res/values/donottranslate.xml b/android/res/values/donottranslate.xml index 6a24024b4f..397009c077 100644 --- a/android/res/values/donottranslate.xml +++ b/android/res/values/donottranslate.xml @@ -38,6 +38,8 @@ Settings MapsMePrefs MapStyle + TtsEnabled + TtsLanguage %1$s: %2$s %2$s :%1$s diff --git a/android/res/xml/prefs_headers.xml b/android/res/xml/prefs_headers.xml index bbb02672c6..013d71a81f 100644 --- a/android/res/xml/prefs_headers.xml +++ b/android/res/xml/prefs_headers.xml @@ -4,6 +4,10 @@ android:title="@string/prefs_group_map" android:fragment="com.mapswithme.maps.settings.MapPrefsFragment"/> +
+
diff --git a/android/res/xml/prefs_route.xml b/android/res/xml/prefs_route.xml new file mode 100644 index 0000000000..b7755bb066 --- /dev/null +++ b/android/res/xml/prefs_route.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/android/src/com/mapswithme/maps/settings/RoutePrefsFragment.java b/android/src/com/mapswithme/maps/settings/RoutePrefsFragment.java new file mode 100644 index 0000000000..821c1a6e65 --- /dev/null +++ b/android/src/com/mapswithme/maps/settings/RoutePrefsFragment.java @@ -0,0 +1,131 @@ +package com.mapswithme.maps.settings; + +import android.content.Intent; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.preference.SwitchPreference; +import android.speech.tts.TextToSpeech; +import android.support.annotation.NonNull; +import com.mapswithme.maps.R; +import com.mapswithme.maps.sound.LanguageData; +import com.mapswithme.maps.sound.TtsPlayer; +import com.mapswithme.util.Config; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RoutePrefsFragment extends PreferenceFragment +{ + private static final int REQUEST_INSTALL_DATA = 1; + + private SwitchPreference mPrefEnabled; + private ListPreference mPrefLanguages; + + private final Map mLanguages = new HashMap<>(); + private String mSelectedLanguage; + + private void setLanguage(@NonNull LanguageData lang) + { + TtsPlayer.INSTANCE.setLanguage(lang); + mPrefLanguages.setSummary(lang.name); + + Config.setTtsLanguageSetByUser(); + update(); + } + + private void update() + { + List languages = TtsPlayer.INSTANCE.getAvailableLanguages(true); + mLanguages.clear(); + + if (languages.isEmpty()) + { + mPrefEnabled.setEnabled(false); + mPrefEnabled.setSummary(R.string.pref_tts_unavailable); + mPrefLanguages.setEnabled(false); + return; + } + + mPrefEnabled.setChecked(TtsPlayer.INSTANCE.isEnabled()); + + final CharSequence[] entries = new CharSequence[languages.size()]; + final CharSequence[] values = new CharSequence[languages.size()]; + for (int i = 0; i < languages.size(); i++) + { + LanguageData lang = languages.get(i); + entries[i] = lang.name; + values[i] = lang.internalCode; + + mLanguages.put(lang.internalCode, lang); + } + + mPrefLanguages.setEntries(entries); + mPrefLanguages.setEntryValues(values); + + LanguageData curLang = TtsPlayer.INSTANCE.getSelectedLanguage(languages); + if (curLang != null) + { + mPrefLanguages.setSummary(curLang.name); + mPrefLanguages.setValueIndex(languages.indexOf(curLang)); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.prefs_route); + + mPrefEnabled = (SwitchPreference) findPreference(getString(R.string.pref_tts_enabled)); + mPrefLanguages = (ListPreference) findPreference(getString(R.string.pref_tts_language)); + + update(); + + mPrefEnabled.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() + { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) + { + boolean set = (Boolean)newValue; + mPrefLanguages.setEnabled(set); + Config.setTtsEnabled(set); + TtsPlayer.INSTANCE.setEnabled(set); + return true; + } + }); + + mPrefLanguages.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() + { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) + { + mSelectedLanguage = (String)newValue; + LanguageData lang = mLanguages.get(mSelectedLanguage); + if (lang.getStatus() == TextToSpeech.LANG_MISSING_DATA) + startActivityForResult(new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA), REQUEST_INSTALL_DATA); + else + setLanguage(lang); + return false; + } + }); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) + { + // Do not check resultCode here as it is always RESULT_CANCELED + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == REQUEST_INSTALL_DATA) + { + update(); + + LanguageData lang = mLanguages.get(mSelectedLanguage); + if (lang != null && lang.getStatus() == TextToSpeech.LANG_AVAILABLE) + setLanguage(lang); + } + } +} diff --git a/android/src/com/mapswithme/maps/settings/SettingsActivity.java b/android/src/com/mapswithme/maps/settings/SettingsActivity.java index 0972e0ccce..6c08f10d44 100644 --- a/android/src/com/mapswithme/maps/settings/SettingsActivity.java +++ b/android/src/com/mapswithme/maps/settings/SettingsActivity.java @@ -62,6 +62,8 @@ public class SettingsActivity extends PreferenceActivity { if (header.id == R.id.group_map) Statistics.INSTANCE.trackSimpleNamedEvent(Statistics.EventName.Settings.GROUP_MAP); + else if (header.id == R.id.group_route) + Statistics.INSTANCE.trackSimpleNamedEvent(Statistics.EventName.Settings.GROUP_ROUTE); else if (header.id == R.id.group_misc) Statistics.INSTANCE.trackSimpleNamedEvent(Statistics.EventName.Settings.GROUP_MISC); else if (header.id == R.id.help) diff --git a/android/src/com/mapswithme/util/statistics/Statistics.java b/android/src/com/mapswithme/util/statistics/Statistics.java index 9ea9c640c3..d82a3154ad 100644 --- a/android/src/com/mapswithme/util/statistics/Statistics.java +++ b/android/src/com/mapswithme/util/statistics/Statistics.java @@ -70,6 +70,7 @@ public enum Statistics public static final String ABOUT = "Settings. About."; public static final String COPYRIGHT = "Settings. Copyright."; public static final String GROUP_MAP = "Settings. Group: map."; + public static final String GROUP_ROUTE = "Settings. Group: route."; public static final String GROUP_MISC = "Settings. Group: misc."; private Settings() {} From dc4675a012fef845c862f609fad1f96448e6840e Mon Sep 17 00:00:00 2001 From: Alexander Marchuk Date: Sat, 24 Oct 2015 16:34:15 +0300 Subject: [PATCH 5/8] [android] fix: Code review fixes. --- .../maps/settings/RoutePrefsFragment.java | 6 +++--- .../src/com/mapswithme/maps/sound/TtsPlayer.java | 15 ++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/android/src/com/mapswithme/maps/settings/RoutePrefsFragment.java b/android/src/com/mapswithme/maps/settings/RoutePrefsFragment.java index 821c1a6e65..e28da4ca1e 100644 --- a/android/src/com/mapswithme/maps/settings/RoutePrefsFragment.java +++ b/android/src/com/mapswithme/maps/settings/RoutePrefsFragment.java @@ -49,7 +49,7 @@ public class RoutePrefsFragment extends PreferenceFragment return; } - mPrefEnabled.setChecked(TtsPlayer.INSTANCE.isEnabled()); + mPrefEnabled.setChecked(TtsPlayer.isEnabled()); final CharSequence[] entries = new CharSequence[languages.size()]; final CharSequence[] values = new CharSequence[languages.size()]; @@ -65,7 +65,7 @@ public class RoutePrefsFragment extends PreferenceFragment mPrefLanguages.setEntries(entries); mPrefLanguages.setEntryValues(values); - LanguageData curLang = TtsPlayer.INSTANCE.getSelectedLanguage(languages); + LanguageData curLang = TtsPlayer.getSelectedLanguage(languages); if (curLang != null) { mPrefLanguages.setSummary(curLang.name); @@ -92,7 +92,7 @@ public class RoutePrefsFragment extends PreferenceFragment boolean set = (Boolean)newValue; mPrefLanguages.setEnabled(set); Config.setTtsEnabled(set); - TtsPlayer.INSTANCE.setEnabled(set); + TtsPlayer.setEnabled(set); return true; } }); diff --git a/android/src/com/mapswithme/maps/sound/TtsPlayer.java b/android/src/com/mapswithme/maps/sound/TtsPlayer.java index d154e8b27a..7871459ffe 100644 --- a/android/src/com/mapswithme/maps/sound/TtsPlayer.java +++ b/android/src/com/mapswithme/maps/sound/TtsPlayer.java @@ -3,6 +3,7 @@ package com.mapswithme.maps.sound; import android.content.Context; import android.content.res.Resources; import android.speech.tts.TextToSpeech; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; @@ -32,7 +33,7 @@ public enum TtsPlayer TtsPlayer() {} - private @Nullable LanguageData findSupportedLanguage(String internalCode, List langs) + private static @Nullable LanguageData findSupportedLanguage(String internalCode, List langs) { if (TextUtils.isEmpty(internalCode)) return null; @@ -44,7 +45,7 @@ public enum TtsPlayer return null; } - private @Nullable LanguageData findSupportedLanguage(Locale locale, List langs) + private static @Nullable LanguageData findSupportedLanguage(Locale locale, List langs) { if (locale == null) return null; @@ -69,7 +70,7 @@ public enum TtsPlayer setLanguageInternal(lang); } - private LanguageData getDefaultLanguage(List langs) + private @Nullable LanguageData getDefaultLanguage(List langs) { Locale defLocale = Locale.getDefault(); if (defLocale == null) @@ -86,7 +87,7 @@ public enum TtsPlayer return lang; } - public LanguageData getSelectedLanguage(List langs) + public static @Nullable LanguageData getSelectedLanguage(List langs) { return findSupportedLanguage(Config.getTtsLanguage(), langs); } @@ -165,17 +166,17 @@ public enum TtsPlayer mTts.stop(); } - public boolean isEnabled() + public static boolean isEnabled() { return nativeAreTurnNotificationsEnabled(); } - public void setEnabled(boolean enabled) + public static void setEnabled(boolean enabled) { nativeEnableTurnNotifications(enabled); } - public List getAvailableLanguages(boolean includeDownloadable) + public @NonNull List getAvailableLanguages(boolean includeDownloadable) { List res = new ArrayList<>(); if (mUnavailable || mTts == null) From ff306d7f0fc72f214ec70e3f58d6f4f21502969b Mon Sep 17 00:00:00 2001 From: Alexander Marchuk Date: Tue, 27 Oct 2015 22:20:27 +0300 Subject: [PATCH 6/8] [android] refactor: More complex TTS initialization logic. See the next commit for details. --- android/res/values/strings-tts.xml | 24 ++-- android/res/xml/prefs_route.xml | 3 +- .../maps/settings/RoutePrefsFragment.java | 113 ++++++++++------ .../mapswithme/maps/sound/LanguageData.java | 41 +++--- .../com/mapswithme/maps/sound/TtsPlayer.java | 125 ++++++++++-------- android/src/com/mapswithme/util/Config.java | 11 -- 6 files changed, 178 insertions(+), 139 deletions(-) diff --git a/android/res/values/strings-tts.xml b/android/res/values/strings-tts.xml index e96013793a..39306a3bc3 100644 --- a/android/res/values/strings-tts.xml +++ b/android/res/values/strings-tts.xml @@ -1,15 +1,15 @@