blob: 018ac7affd4d45547777689504b2cc0a83580bde [file] [log] [blame]
// 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