diff --git a/android/app/src/main/cpp/app/organicmaps/UserMarkHelper.cpp b/android/app/src/main/cpp/app/organicmaps/UserMarkHelper.cpp index 637352fd6..7a555d836 100644 --- a/android/app/src/main/cpp/app/organicmaps/UserMarkHelper.cpp +++ b/android/app/src/main/cpp/app/organicmaps/UserMarkHelper.cpp @@ -74,7 +74,7 @@ jobject CreateMapObject(JNIEnv * env, place_page::Info const & info, int mapObje (jint)fID.m_index)); jni::TScopedLocalRef jTitle(env, jni::ToJavaString(env, info.GetTitle())); jni::TScopedLocalRef jSecondaryTitle(env, jni::ToJavaString(env, info.GetSecondaryTitle())); - jni::TScopedLocalRef jSubtitle(env, jni::ToJavaString(env, info.GetSubtitle())); + jni::TScopedLocalRef jSubtitle(env, jni::ToJavaStringWithSupplementalCharsFix(env, info.GetSubtitle())); jni::TScopedLocalRef jAddress(env, jni::ToJavaString(env, info.GetSecondarySubtitle())); jni::TScopedLocalRef jApiId(env, jni::ToJavaString(env, parseApi ? info.GetApiUrl() : "")); jni::TScopedLocalRef jWikiDescription(env, jni::ToJavaString(env, info.GetWikiDescription())); @@ -118,7 +118,7 @@ jobject CreateBookmark(JNIEnv *env, const place_page::Info &info, (jlong)info.GetID().GetMwmVersion(), (jint)info.GetID().m_index)); jni::TScopedLocalRef jTitle(env, jni::ToJavaString(env, info.GetTitle())); jni::TScopedLocalRef jSecondaryTitle(env, jni::ToJavaString(env, info.GetSecondaryTitle())); - jni::TScopedLocalRef jSubtitle(env, jni::ToJavaString(env, info.GetSubtitle())); + jni::TScopedLocalRef jSubtitle(env, jni::ToJavaStringWithSupplementalCharsFix(env, info.GetSubtitle())); jni::TScopedLocalRef jAddress(env, jni::ToJavaString(env, info.GetSecondarySubtitle())); jni::TScopedLocalRef jWikiDescription(env, jni::ToJavaString(env, info.GetWikiDescription())); jobject mapObject = env->NewObject( diff --git a/android/app/src/main/cpp/app/organicmaps/core/jni_helper.cpp b/android/app/src/main/cpp/app/organicmaps/core/jni_helper.cpp index 1329ab9ea..74d048e11 100644 --- a/android/app/src/main/cpp/app/organicmaps/core/jni_helper.cpp +++ b/android/app/src/main/cpp/app/organicmaps/core/jni_helper.cpp @@ -28,6 +28,21 @@ jclass g_elevationInfoClazz; extern "C" { +int __system_property_get(char const * name, char * value); + +static bool IsAndroidLowerThan7() +{ + char value[92] = { 0 }; + if (__system_property_get("ro.build.version.sdk", value) < 1) + return false; + const int apiLevel = atoi(value); + if (apiLevel > 0 && apiLevel < 24) + return true; + return false; +} + +static bool const g_isAndroidLowerThan7 = IsAndroidLowerThan7(); + JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM * jvm, void *) { @@ -154,6 +169,24 @@ jstring ToJavaString(JNIEnv * env, char const * s) return env->NewStringUTF(s); } +jstring ToJavaStringWithSupplementalCharsFix(JNIEnv * env, std::string const & s) +{ + // Android 5 and 6 do not support unicode characters greater than 0xFFFF encoded in UTF-8. + if (g_isAndroidLowerThan7) + { + // Detect 4-byte sequence start marker to avoid unnecessary allocation + copy. + for (const auto c : s) + { + if (0b11110000 == (c & 0b11111000)) + { + const auto utf16 = strings::ToUtf16(s); + return env->NewString(reinterpret_cast(utf16.data()), utf16.size()); + } + } + } + return env->NewStringUTF(s.c_str()); +} + jclass GetStringClass(JNIEnv * env) { return env->FindClass(GetStringClassName()); diff --git a/android/app/src/main/cpp/app/organicmaps/core/jni_helper.hpp b/android/app/src/main/cpp/app/organicmaps/core/jni_helper.hpp index fd9465dc5..3753e7b08 100644 --- a/android/app/src/main/cpp/app/organicmaps/core/jni_helper.hpp +++ b/android/app/src/main/cpp/app/organicmaps/core/jni_helper.hpp @@ -55,6 +55,9 @@ inline jstring ToJavaString(JNIEnv * env, std::string_view sv) return ToJavaString(env, std::string(sv).c_str()); } +// Remove after dropping Android 5 and 6 support. +jstring ToJavaStringWithSupplementalCharsFix(JNIEnv * env, std::string const & s); + jclass GetStringClass(JNIEnv * env); char const * GetStringClassName();