diff --git a/android/app/src/main/java/app/organicmaps/sdk/sound/TtsPlayer.java b/android/app/src/main/java/app/organicmaps/sdk/sound/TtsPlayer.java index 0fc8a9b6c..2be401958 100644 --- a/android/app/src/main/java/app/organicmaps/sdk/sound/TtsPlayer.java +++ b/android/app/src/main/java/app/organicmaps/sdk/sound/TtsPlayer.java @@ -1,7 +1,6 @@ package app.organicmaps.sdk.sound; import android.content.Context; -import android.content.res.Resources; import android.database.ContentObserver; import android.os.Bundle; import android.os.Handler; @@ -10,9 +9,9 @@ import android.provider.Settings; import android.speech.tts.TextToSpeech; import android.speech.tts.UtteranceProgressListener; import android.text.TextUtils; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import app.organicmaps.R; import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.concurrency.UiThread; import app.organicmaps.sdk.util.log.Logger; @@ -43,6 +42,9 @@ public enum TtsPlayer private static final float SPEECH_RATE = 1.0f; private static final int TTS_SPEAK_DELAY_MILLIS = 50; + @Nullable + private static List> sSupportedLanguages = null; + public static Runnable sOnReloadCallback = null; private ContentObserver mTtsEngineObserver; @@ -286,19 +288,15 @@ public enum TtsPlayer private boolean getUsableLanguages(List outList) { - Resources resources = mContext.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++) + for (final Pair langNamePair : getSupportedLanguages()) { try { - outList.add(new LanguageData(codes[i], names[i], mTts)); + outList.add(new LanguageData(langNamePair.first, langNamePair.second, mTts)); } - catch (LanguageData.NotAvailableException ignored) + catch (LanguageData.NotAvailableException ex) { - Logger.w(TAG, "Failed to get usable languages " + ignored.getMessage()); + Logger.w(TAG, "Failed to get usable languages " + ex.getMessage()); } catch (IllegalArgumentException e) { @@ -351,8 +349,20 @@ public enum TtsPlayer return res; } + @NonNull + private List> getSupportedLanguages() + { + if (sSupportedLanguages == null) + { + sSupportedLanguages = nativeGetSupportedLanguages(); + } + return sSupportedLanguages; + } + private native static void nativeEnableTurnNotifications(boolean enable); private native static boolean nativeAreTurnNotificationsEnabled(); private native static void nativeSetTurnNotificationsLocale(String code); private native static String nativeGetTurnNotificationsLocale(); + @NonNull + private native static List> nativeGetSupportedLanguages(); } diff --git a/android/app/src/main/res/values/strings-tts.xml b/android/app/src/main/res/values/strings-tts.xml deleted file mode 100644 index 4e2dc74cf..000000000 --- a/android/app/src/main/res/values/strings-tts.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - - en - id - ca - da - de - es-ES:es - es-MX:es-MX - eu - fr - hr - it - sw - hu - nl - nb - pl - pt-PT:pt - pt-BR:pt-BR - ro - sk - fi - sv - vi - tr - cs - el - be - bg - ru - sr - uk - ar - fa - mr - hi - th - zh-CN:zh-Hans - zh-TW:zh-Hant - ja - ko - - - - English - Bahasa Indonesia - Català - Dansk - Deutsch - Español - Español (México) - Euskara - Français - Hrvatski - Italiano - Kiswahili - Magyar - Nederlands - Norsk Bokmål - Polski - Português - Português (Brasil) - Română - Slovenčina - Suomi - Svenska - Tiếng Việt - Türkçe - Čeština - Ελληνικά - Беларуская - Български - Русский - Српски - Українська - العربية - فارسی - मराठी - हिन्दी - ไทย - 中文简体 - 中文繁體 - 日本語 - 한국어 - - diff --git a/android/sdk/src/main/cpp/app/organicmaps/sdk/sound/tts.cpp b/android/sdk/src/main/cpp/app/organicmaps/sdk/sound/tts.cpp index 3fabf7596..a40287c90 100644 --- a/android/sdk/src/main/cpp/app/organicmaps/sdk/sound/tts.cpp +++ b/android/sdk/src/main/cpp/app/organicmaps/sdk/sound/tts.cpp @@ -1,30 +1,51 @@ #include "app/organicmaps/sdk/Framework.hpp" #include "app/organicmaps/sdk/core/jni_helper.hpp" +#include "app/organicmaps/sdk/core/jni_java_methods.hpp" + +#include "platform/languages.hpp" extern "C" { - JNIEXPORT void JNICALL - Java_app_organicmaps_sdk_sound_TtsPlayer_nativeEnableTurnNotifications(JNIEnv *, jclass, jboolean enable) - { - return frm()->GetRoutingManager().EnableTurnNotifications(static_cast(enable)); - } +JNIEXPORT void JNICALL +Java_app_organicmaps_sdk_sound_TtsPlayer_nativeEnableTurnNotifications(JNIEnv *, jclass, jboolean enable) +{ + return frm()->GetRoutingManager().EnableTurnNotifications(static_cast(enable)); +} - JNIEXPORT jboolean JNICALL - Java_app_organicmaps_sdk_sound_TtsPlayer_nativeAreTurnNotificationsEnabled(JNIEnv *, jclass) - { - return static_cast(frm()->GetRoutingManager().AreTurnNotificationsEnabled()); - } +JNIEXPORT jboolean JNICALL +Java_app_organicmaps_sdk_sound_TtsPlayer_nativeAreTurnNotificationsEnabled(JNIEnv *, jclass) +{ + return static_cast(frm()->GetRoutingManager().AreTurnNotificationsEnabled()); +} - JNIEXPORT void JNICALL - Java_app_organicmaps_sdk_sound_TtsPlayer_nativeSetTurnNotificationsLocale(JNIEnv * env, jclass, jstring jLocale) - { - frm()->GetRoutingManager().SetTurnNotificationsLocale(jni::ToNativeString(env, jLocale)); - } +JNIEXPORT void JNICALL +Java_app_organicmaps_sdk_sound_TtsPlayer_nativeSetTurnNotificationsLocale(JNIEnv * env, jclass, jstring jLocale) +{ + frm()->GetRoutingManager().SetTurnNotificationsLocale(jni::ToNativeString(env, jLocale)); +} - JNIEXPORT jstring JNICALL - Java_app_organicmaps_sdk_sound_TtsPlayer_nativeGetTurnNotificationsLocale(JNIEnv * env, jclass) +JNIEXPORT jstring JNICALL +Java_app_organicmaps_sdk_sound_TtsPlayer_nativeGetTurnNotificationsLocale(JNIEnv * env, jclass) +{ + return jni::ToJavaString(env, frm()->GetRoutingManager().GetTurnNotificationsLocale()); +} + +JNIEXPORT jobject JNICALL +Java_app_organicmaps_sdk_sound_TtsPlayer_nativeGetSupportedLanguages(JNIEnv * env, jclass) +{ + auto const & supportedLanguages = routing::turns::sound::kLanguageList; + + auto const & listBuilder = jni::ListBuilder::Instance(env); + jobject const list = listBuilder.CreateArray(env, supportedLanguages.size()); + for (auto const & [lang, name] : supportedLanguages) { - return jni::ToJavaString(env, frm()->GetRoutingManager().GetTurnNotificationsLocale()); + jni::TScopedLocalRef const jLangString(env, jni::ToJavaString(env, lang)); + jni::TScopedLocalRef const jNameString(env, jni::ToJavaString(env, name)); + jni::TScopedLocalRef const pair(env, + jni::PairBuilder::Instance(env).Create(env, jLangString.get(), jNameString.get())); + env->CallBooleanMethod(list, listBuilder.m_add, pair.get()); } -} // extern "C" + return list; +} +} // extern "C" diff --git a/libs/platform/languages.hpp b/libs/platform/languages.hpp index f11b33d2e..9f912dbfa 100644 --- a/libs/platform/languages.hpp +++ b/libs/platform/languages.hpp @@ -4,13 +4,29 @@ #include #include -// The list of languages which can be used by TTS. -// It shall be included in Android (jni) and iOS parts to get the languages list. -// TODO: Now it is used only on iOS. -// Manual sync with android/res/values/strings-tts.xml is needed. +#include "std/target_os.hpp" namespace routing::turns::sound { +/** + * @brief The list of languages which can be used by TTS (Text-To-Speech). + * + * Supported language identifiers follow the format: + * @code + * language[-COUNTRY][:internal_code] + * @endcode + * + * Where: + * - `language`: a two-letter ISO 639-1 language code (e.g., "en", "fr", "zh"). + * - `COUNTRY`: optional two-letter ISO 3166-1 alpha-2 country code (e.g., "US", "CN", "TW"). + * - `internal_code`: optional internal language code used by the TTS core. + * If not specified, `language` is used as the default. + * + * @note Special handling for Chinese: + * - `zh_TW`, `zh_MO`, and `zh_HK` are treated as `zh-Hant` (Traditional Chinese). + * - All other variants default to `zh-Hans` (Simplified Chinese). + * + */ std::array, 40> constexpr kLanguageList = {{ {"en", "English"}, @@ -18,8 +34,13 @@ std::array, 40> constexpr kLanguag {"ca", "Català"}, {"da", "Dansk"}, {"de", "Deutsch"}, +#ifdef OMIM_OS_ANDROID + {"es-ES:es", "Español"}, + {"es-MX:es-MX", "Español (México)"}, +#else {"es", "Español"}, {"es-MX", "Español (México)"}, +#endif {"eu", "Euskara"}, {"fr", "Français"}, {"hr", "Hrvatski"}, @@ -29,8 +50,13 @@ std::array, 40> constexpr kLanguag {"nl", "Nederlands"}, {"nb", "Norsk Bokmål"}, {"pl", "Polski"}, +#ifdef OMIM_OS_ANDROID + {"pt-PT:pt", "Português"}, + {"pt-BR:pt-BR", "Português (Brasil)"}, +#else {"pt", "Português"}, {"pt-BR", "Português (Brasil)"}, +#endif {"ro", "Română"}, {"sk", "Slovenčina"}, {"fi", "Suomi"}, @@ -49,8 +75,13 @@ std::array, 40> constexpr kLanguag {"mr", "मराठी"}, {"hi", "हिंदी"}, {"th", "ไทย"}, +#ifdef OMIM_OS_ANDROID + {"zh-CN:zh-Hans", "中文简体"}, + {"zh-TW:zh-Hant", "中文繁體"}, +#else {"zh-Hans", "中文简体"}, {"zh-Hant", "中文繁體"}, +#endif {"ja", "日本語"}, {"ko", "한국어"}, }};