| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "flutter/fml/platform/android/jni_util.h" |
| |
| #include <sys/prctl.h> |
| |
| #include <memory> |
| #include <string> |
| |
| #include "flutter/fml/logging.h" |
| #include "flutter/fml/string_conversion.h" |
| |
| namespace fml { |
| namespace jni { |
| |
| static JavaVM* g_jvm = nullptr; |
| |
| #define ASSERT_NO_EXCEPTION() FML_CHECK(env->ExceptionCheck() == JNI_FALSE); |
| |
| struct JNIDetach { |
| ~JNIDetach() { DetachFromVM(); } |
| }; |
| |
| // Thread-local object that will detach from JNI during thread shutdown; |
| static thread_local std::unique_ptr<JNIDetach> tls_jni_detach; |
| |
| void InitJavaVM(JavaVM* vm) { |
| FML_DCHECK(g_jvm == nullptr); |
| g_jvm = vm; |
| } |
| |
| JNIEnv* AttachCurrentThread() { |
| FML_DCHECK(g_jvm != nullptr) |
| << "Trying to attach to current thread without calling InitJavaVM first."; |
| |
| JNIEnv* env = nullptr; |
| if (g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4) == |
| JNI_OK) { |
| return env; |
| } |
| |
| JavaVMAttachArgs args; |
| args.version = JNI_VERSION_1_4; |
| args.group = nullptr; |
| // 16 is the maximum size for thread names on Android. |
| char thread_name[16]; |
| int err = prctl(PR_GET_NAME, thread_name); |
| if (err < 0) { |
| args.name = nullptr; |
| } else { |
| args.name = thread_name; |
| } |
| [[maybe_unused]] jint ret = g_jvm->AttachCurrentThread(&env, &args); |
| FML_DCHECK(JNI_OK == ret); |
| |
| FML_DCHECK(tls_jni_detach.get() == nullptr); |
| tls_jni_detach.reset(new JNIDetach()); |
| |
| return env; |
| } |
| |
| void DetachFromVM() { |
| if (g_jvm) { |
| g_jvm->DetachCurrentThread(); |
| } |
| } |
| |
| std::string JavaStringToString(JNIEnv* env, jstring str) { |
| if (env == nullptr || str == nullptr) { |
| return ""; |
| } |
| const jchar* chars = env->GetStringChars(str, NULL); |
| if (chars == nullptr) { |
| return ""; |
| } |
| std::u16string u16_string(reinterpret_cast<const char16_t*>(chars), |
| env->GetStringLength(str)); |
| std::string u8_string = Utf16ToUtf8(u16_string); |
| env->ReleaseStringChars(str, chars); |
| ASSERT_NO_EXCEPTION(); |
| return u8_string; |
| } |
| |
| ScopedJavaLocalRef<jstring> StringToJavaString(JNIEnv* env, |
| const std::string& u8_string) { |
| std::u16string u16_string = Utf8ToUtf16(u8_string); |
| auto result = ScopedJavaLocalRef<jstring>( |
| env, env->NewString(reinterpret_cast<const jchar*>(u16_string.data()), |
| u16_string.length())); |
| ASSERT_NO_EXCEPTION(); |
| return result; |
| } |
| |
| std::vector<std::string> StringArrayToVector(JNIEnv* env, jobjectArray array) { |
| std::vector<std::string> out; |
| if (env == nullptr || array == nullptr) { |
| return out; |
| } |
| |
| jsize length = env->GetArrayLength(array); |
| |
| if (length == -1) { |
| return out; |
| } |
| |
| out.resize(length); |
| for (jsize i = 0; i < length; ++i) { |
| ScopedJavaLocalRef<jstring> java_string( |
| env, static_cast<jstring>(env->GetObjectArrayElement(array, i))); |
| out[i] = JavaStringToString(env, java_string.obj()); |
| } |
| |
| return out; |
| } |
| |
| std::vector<std::string> StringListToVector(JNIEnv* env, jobject list) { |
| std::vector<std::string> out; |
| if (env == nullptr || list == nullptr) { |
| return out; |
| } |
| |
| ScopedJavaLocalRef<jclass> list_clazz(env, env->FindClass("java/util/List")); |
| FML_DCHECK(!list_clazz.is_null()); |
| |
| jmethodID list_get = |
| env->GetMethodID(list_clazz.obj(), "get", "(I)Ljava/lang/Object;"); |
| jmethodID list_size = env->GetMethodID(list_clazz.obj(), "size", "()I"); |
| |
| jint size = env->CallIntMethod(list, list_size); |
| |
| if (size == 0) { |
| return out; |
| } |
| |
| out.resize(size); |
| for (jint i = 0; i < size; ++i) { |
| ScopedJavaLocalRef<jstring> java_string( |
| env, static_cast<jstring>(env->CallObjectMethod(list, list_get, i))); |
| out[i] = JavaStringToString(env, java_string.obj()); |
| } |
| |
| return out; |
| } |
| |
| ScopedJavaLocalRef<jobjectArray> VectorToStringArray( |
| JNIEnv* env, |
| const std::vector<std::string>& vector) { |
| FML_DCHECK(env); |
| ScopedJavaLocalRef<jclass> string_clazz(env, |
| env->FindClass("java/lang/String")); |
| FML_DCHECK(!string_clazz.is_null()); |
| jobjectArray java_array = |
| env->NewObjectArray(vector.size(), string_clazz.obj(), NULL); |
| ASSERT_NO_EXCEPTION(); |
| for (size_t i = 0; i < vector.size(); ++i) { |
| ScopedJavaLocalRef<jstring> item = StringToJavaString(env, vector[i]); |
| env->SetObjectArrayElement(java_array, i, item.obj()); |
| } |
| return ScopedJavaLocalRef<jobjectArray>(env, java_array); |
| } |
| |
| ScopedJavaLocalRef<jobjectArray> VectorToBufferArray( |
| JNIEnv* env, |
| const std::vector<std::vector<uint8_t>>& vector) { |
| FML_DCHECK(env); |
| ScopedJavaLocalRef<jclass> byte_buffer_clazz( |
| env, env->FindClass("java/nio/ByteBuffer")); |
| FML_DCHECK(!byte_buffer_clazz.is_null()); |
| jobjectArray java_array = |
| env->NewObjectArray(vector.size(), byte_buffer_clazz.obj(), NULL); |
| ASSERT_NO_EXCEPTION(); |
| for (size_t i = 0; i < vector.size(); ++i) { |
| uint8_t* data = const_cast<uint8_t*>(vector[i].data()); |
| ScopedJavaLocalRef<jobject> item( |
| env, env->NewDirectByteBuffer(reinterpret_cast<void*>(data), |
| vector[i].size())); |
| env->SetObjectArrayElement(java_array, i, item.obj()); |
| } |
| return ScopedJavaLocalRef<jobjectArray>(env, java_array); |
| } |
| |
| bool HasException(JNIEnv* env) { |
| return env->ExceptionCheck() != JNI_FALSE; |
| } |
| |
| bool ClearException(JNIEnv* env, bool silent) { |
| if (!HasException(env)) { |
| return false; |
| } |
| if (!silent) { |
| env->ExceptionDescribe(); |
| } |
| env->ExceptionClear(); |
| return true; |
| } |
| |
| bool CheckException(JNIEnv* env) { |
| if (!HasException(env)) { |
| return true; |
| } |
| |
| jthrowable exception = env->ExceptionOccurred(); |
| env->ExceptionClear(); |
| FML_LOG(ERROR) << fml::jni::GetJavaExceptionInfo(env, exception); |
| env->DeleteLocalRef(exception); |
| return false; |
| } |
| |
| std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { |
| ScopedJavaLocalRef<jclass> throwable_clazz( |
| env, env->FindClass("java/lang/Throwable")); |
| jmethodID throwable_printstacktrace = env->GetMethodID( |
| throwable_clazz.obj(), "printStackTrace", "(Ljava/io/PrintStream;)V"); |
| |
| // Create an instance of ByteArrayOutputStream. |
| ScopedJavaLocalRef<jclass> bytearray_output_stream_clazz( |
| env, env->FindClass("java/io/ByteArrayOutputStream")); |
| jmethodID bytearray_output_stream_constructor = |
| env->GetMethodID(bytearray_output_stream_clazz.obj(), "<init>", "()V"); |
| jmethodID bytearray_output_stream_tostring = env->GetMethodID( |
| bytearray_output_stream_clazz.obj(), "toString", "()Ljava/lang/String;"); |
| ScopedJavaLocalRef<jobject> bytearray_output_stream( |
| env, env->NewObject(bytearray_output_stream_clazz.obj(), |
| bytearray_output_stream_constructor)); |
| |
| // Create an instance of PrintStream. |
| ScopedJavaLocalRef<jclass> printstream_clazz( |
| env, env->FindClass("java/io/PrintStream")); |
| jmethodID printstream_constructor = env->GetMethodID( |
| printstream_clazz.obj(), "<init>", "(Ljava/io/OutputStream;)V"); |
| ScopedJavaLocalRef<jobject> printstream( |
| env, env->NewObject(printstream_clazz.obj(), printstream_constructor, |
| bytearray_output_stream.obj())); |
| |
| // Call Throwable.printStackTrace(PrintStream) |
| env->CallVoidMethod(java_throwable, throwable_printstacktrace, |
| printstream.obj()); |
| |
| // Call ByteArrayOutputStream.toString() |
| ScopedJavaLocalRef<jstring> exception_string( |
| env, |
| static_cast<jstring>(env->CallObjectMethod( |
| bytearray_output_stream.obj(), bytearray_output_stream_tostring))); |
| if (ClearException(env)) { |
| return "Java OOM'd in exception handling, check logcat"; |
| } |
| |
| return JavaStringToString(env, exception_string.obj()); |
| } |
| |
| } // namespace jni |
| } // namespace fml |