/* * Copyright (C) 2012, Collabora Ltd. * Author: Sebastian Dröge * Copyright (C) 2013, Fluendo S.A. * Author: Andoni Morales * Copyright (C) 2014, Sebastian Dröge * Copyright (C) 2014, Collabora Ltd. * Author: Matthieu Bouron * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "gstjniutils.h" static GModule *java_module; static jint (*get_created_java_vms) (JavaVM ** vmBuf, jsize bufLen, jsize * nVMs); static jint (*create_java_vm) (JavaVM ** p_vm, JNIEnv ** p_env, void *vm_args); static JavaVM *java_vm; static gboolean initialized = FALSE; static gboolean started_java_vm = FALSE; static pthread_key_t current_jni_env; jclass gst_amc_jni_get_class (JNIEnv * env, const gchar * name) { jclass tmp, ret = NULL; GST_DEBUG ("Retrieving Java class %s", name); tmp = (*env)->FindClass (env, name); if (!tmp) { ret = FALSE; (*env)->ExceptionClear (env); GST_ERROR ("Failed to get %s class", name); goto done; } ret = (*env)->NewGlobalRef (env, tmp); if (!ret) { (*env)->ExceptionClear (env); GST_ERROR ("Failed to get %s class global reference", name); } done: if (tmp) (*env)->DeleteLocalRef (env, tmp); tmp = NULL; return ret; } jmethodID gst_amc_jni_get_method (JNIEnv * env, jclass klass, const gchar * name, const gchar * signature) { jmethodID ret; ret = (*env)->GetMethodID (env, klass, name, signature); if (!ret) { (*env)->ExceptionClear (env); GST_ERROR ("Failed to get method ID %s", name); } return ret; } jmethodID gst_amc_jni_get_static_method (JNIEnv * env, jclass klass, const gchar * name, const gchar * signature) { jmethodID ret; ret = (*env)->GetStaticMethodID (env, klass, name, signature); if (!ret) { (*env)->ExceptionClear (env); GST_ERROR ("Failed to get static method id %s", name); } return ret; } jfieldID gst_amc_jni_get_field_id (JNIEnv * env, jclass klass, const gchar * name, const gchar * type) { jfieldID ret; ret = (*env)->GetFieldID (env, klass, name, type); if (!ret) { (*env)->ExceptionClear (env); GST_ERROR ("Failed to get field ID %s", name); } return ret; } jobject gst_amc_jni_new_object (JNIEnv * env, jclass klass, jmethodID constructor, ...) { jobject tmp; va_list args; va_start (args, constructor); tmp = (*env)->NewObjectV (env, klass, constructor, args); va_end (args); if (!tmp) { (*env)->ExceptionClear (env); GST_ERROR ("Failed to create object"); return NULL; } return gst_amc_jni_object_make_global (env, tmp); } jobject gst_amc_jni_new_object_from_static (JNIEnv * env, jclass klass, jmethodID method, ...) { jobject tmp; va_list args; va_start (args, method); tmp = (*env)->CallStaticObjectMethodV (env, klass, method, args); va_end (args); if ((*env)->ExceptionCheck (env) || !tmp) { (*env)->ExceptionClear (env); GST_ERROR ("Failed to create object from static method"); return NULL; } return gst_amc_jni_object_make_global (env, tmp); } jobject gst_amc_jni_object_make_global (JNIEnv * env, jobject object) { jobject ret; ret = (*env)->NewGlobalRef (env, object); if (!ret) { GST_ERROR ("Failed to create global reference"); (*env)->ExceptionClear (env); } else { gst_amc_jni_object_local_unref (env, object); } return ret; } jobject gst_amc_jni_object_ref (JNIEnv * env, jobject object) { jobject ret; ret = (*env)->NewGlobalRef (env, object); if (!ret) { GST_ERROR ("Failed to create global reference"); (*env)->ExceptionClear (env); } return ret; } void gst_amc_jni_object_unref (JNIEnv * env, jobject object) { (*env)->DeleteGlobalRef (env, object); } void gst_amc_jni_object_local_unref (JNIEnv * env, jobject object) { (*env)->DeleteLocalRef (env, object); } jstring gst_amc_jni_string_from_gchar (JNIEnv * env, const gchar * string) { return (*env)->NewStringUTF (env, string); } gchar * gst_amc_jni_string_to_gchar (JNIEnv * env, jstring string, gboolean release) { const gchar *s = NULL; gchar *ret = NULL; s = (*env)->GetStringUTFChars (env, string, NULL); if (!s) { GST_ERROR ("Failed to convert string to UTF8"); (*env)->ExceptionClear (env); return ret; } ret = g_strdup (s); (*env)->ReleaseStringUTFChars (env, string, s); if (release) { (*env)->DeleteLocalRef (env, string); } return ret; } /* getExceptionSummary() and getStackTrace() taken from Android's * platform/libnativehelper/JNIHelp.cpp * Modified to work with normal C strings and without C++. * * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Returns a human-readable summary of an exception object. The buffer will * be populated with the "binary" class name and, if present, the * exception message. */ static gchar * getExceptionSummary (JNIEnv * env, jthrowable exception) { GString *gs = g_string_new (""); jclass exceptionClass = NULL, classClass = NULL; jmethodID classGetNameMethod, getMessage; jstring classNameStr = NULL, messageStr = NULL; const char *classNameChars, *messageChars; /* get the name of the exception's class */ exceptionClass = (*env)->GetObjectClass (env, exception); classClass = (*env)->GetObjectClass (env, exceptionClass); classGetNameMethod = (*env)->GetMethodID (env, classClass, "getName", "()Ljava/lang/String;"); classNameStr = (jstring) (*env)->CallObjectMethod (env, exceptionClass, classGetNameMethod); if (classNameStr == NULL) { if ((*env)->ExceptionCheck (env)) (*env)->ExceptionClear (env); g_string_append (gs, ""); goto done; } classNameChars = (*env)->GetStringUTFChars (env, classNameStr, NULL); if (classNameChars == NULL) { if ((*env)->ExceptionCheck (env)) (*env)->ExceptionClear (env); g_string_append (gs, ""); goto done; } g_string_append (gs, classNameChars); (*env)->ReleaseStringUTFChars (env, classNameStr, classNameChars); /* if the exception has a detail message, get that */ getMessage = (*env)->GetMethodID (env, exceptionClass, "getMessage", "()Ljava/lang/String;"); messageStr = (jstring) (*env)->CallObjectMethod (env, exception, getMessage); if (messageStr == NULL) { if ((*env)->ExceptionCheck (env)) (*env)->ExceptionClear (env); goto done; } g_string_append (gs, ": "); messageChars = (*env)->GetStringUTFChars (env, messageStr, NULL); if (messageChars != NULL) { g_string_append (gs, messageChars); (*env)->ReleaseStringUTFChars (env, messageStr, messageChars); } else { if ((*env)->ExceptionCheck (env)) (*env)->ExceptionClear (env); g_string_append (gs, ""); } done: if (exceptionClass) (*env)->DeleteLocalRef (env, exceptionClass); if (classClass) (*env)->DeleteLocalRef (env, classClass); if (classNameStr) (*env)->DeleteLocalRef (env, classNameStr); if (messageStr) (*env)->DeleteLocalRef (env, messageStr); return g_string_free (gs, FALSE); } /* * Returns an exception (with stack trace) as a string. */ static gchar * getStackTrace (JNIEnv * env, jthrowable exception) { GString *gs = g_string_new (""); jclass stringWriterClass = NULL, printWriterClass = NULL; jclass exceptionClass = NULL; jmethodID stringWriterCtor, stringWriterToStringMethod; jmethodID printWriterCtor, printStackTraceMethod; jobject stringWriter = NULL, printWriter = NULL; jstring messageStr = NULL; const char *utfChars; stringWriterClass = (*env)->FindClass (env, "java/io/StringWriter"); if (stringWriterClass == NULL) { g_string_append (gs, ""); goto done; } stringWriterCtor = (*env)->GetMethodID (env, stringWriterClass, "", "()V"); stringWriterToStringMethod = (*env)->GetMethodID (env, stringWriterClass, "toString", "()Ljava/lang/String;"); printWriterClass = (*env)->FindClass (env, "java/io/PrintWriter"); if (printWriterClass == NULL) { g_string_append (gs, ""); goto done; } printWriterCtor = (*env)->GetMethodID (env, printWriterClass, "", "(Ljava/io/Writer;)V"); stringWriter = (*env)->NewObject (env, stringWriterClass, stringWriterCtor); if (stringWriter == NULL) { if ((*env)->ExceptionCheck (env)) (*env)->ExceptionClear (env); g_string_append (gs, ""); goto done; } printWriter = (*env)->NewObject (env, printWriterClass, printWriterCtor, stringWriter); if (printWriter == NULL) { if ((*env)->ExceptionCheck (env)) (*env)->ExceptionClear (env); g_string_append (gs, ""); goto done; } exceptionClass = (*env)->GetObjectClass (env, exception); printStackTraceMethod = (*env)->GetMethodID (env, exceptionClass, "printStackTrace", "(Ljava/io/PrintWriter;)V"); (*env)->CallVoidMethod (env, exception, printStackTraceMethod, printWriter); if ((*env)->ExceptionCheck (env)) { (*env)->ExceptionClear (env); g_string_append (gs, ""); goto done; } messageStr = (jstring) (*env)->CallObjectMethod (env, stringWriter, stringWriterToStringMethod); if (messageStr == NULL) { if ((*env)->ExceptionCheck (env)) (*env)->ExceptionClear (env); g_string_append (gs, ""); goto done; } utfChars = (*env)->GetStringUTFChars (env, messageStr, NULL); if (utfChars == NULL) { if ((*env)->ExceptionCheck (env)) (*env)->ExceptionClear (env); g_string_append (gs, ""); goto done; } g_string_append (gs, utfChars); (*env)->ReleaseStringUTFChars (env, messageStr, utfChars); done: if (stringWriterClass) (*env)->DeleteLocalRef (env, stringWriterClass); if (printWriterClass) (*env)->DeleteLocalRef (env, printWriterClass); if (exceptionClass) (*env)->DeleteLocalRef (env, exceptionClass); if (stringWriter) (*env)->DeleteLocalRef (env, stringWriter); if (printWriter) (*env)->DeleteLocalRef (env, printWriter); if (messageStr) (*env)->DeleteLocalRef (env, messageStr); return g_string_free (gs, FALSE); } static JNIEnv * gst_amc_jni_attach_current_thread (void) { JNIEnv *env; JavaVMAttachArgs args; GST_DEBUG ("Attaching thread %p", g_thread_self ()); args.version = JNI_VERSION_1_6; args.name = NULL; args.group = NULL; if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) { GST_ERROR ("Failed to attach current thread"); return NULL; } return env; } static void gst_amc_jni_detach_current_thread (void *env) { GST_DEBUG ("Detaching thread %p", g_thread_self ()); (*java_vm)->DetachCurrentThread (java_vm); } static gboolean check_nativehelper (void) { GModule *module; void **jni_invocation = NULL; gboolean ret = FALSE; module = g_module_open (NULL, G_MODULE_BIND_LOCAL); if (!module) return ret; /* Check if libnativehelper is loaded in the process and if * it has these awful wrappers for JNI_CreateJavaVM and * JNI_GetCreatedJavaVMs that crash the app if you don't * create a JniInvocation instance first. If it isn't we * just fail here and don't initialize anything. * See this code for reference: * https://android.googlesource.com/platform/libnativehelper/+/master/JniInvocation.cpp */ if (!g_module_symbol (module, "_ZN13JniInvocation15jni_invocation_E", (gpointer *) & jni_invocation)) { ret = TRUE; } else { ret = (jni_invocation != NULL && *jni_invocation != NULL); } g_module_close (module); return ret; } static gboolean load_java_module (const gchar * name) { java_module = g_module_open (name, G_MODULE_BIND_LOCAL); if (!java_module) goto load_failed; if (!g_module_symbol (java_module, "JNI_CreateJavaVM", (gpointer *) & create_java_vm)) goto symbol_error; if (!g_module_symbol (java_module, "JNI_GetCreatedJavaVMs", (gpointer *) & get_created_java_vms)) goto symbol_error; return TRUE; load_failed: { GST_ERROR ("Failed to load Java module '%s': %s", GST_STR_NULL (name), g_module_error ()); return FALSE; } symbol_error: { GST_ERROR ("Failed to locate required JNI symbols in '%s': %s", GST_STR_NULL (name), g_module_error ()); g_module_close (java_module); java_module = NULL; return FALSE; } } static gboolean gst_amc_jni_initialize_java_vm (void) { jsize n_vms; /* Returns TRUE if we can safely * a) get the current VMs and * b) start a VM if none is started yet * * FIXME: On Android >= 4.4 we won't be able to safely start a * VM on our own without using private C++ API! */ if (!check_nativehelper ()) { GST_ERROR ("Can't safely check for VMs or start a VM"); return FALSE; } if (!load_java_module (NULL)) { if (!load_java_module ("libdvm")) return FALSE; } n_vms = 0; if (get_created_java_vms (&java_vm, 1, &n_vms) < 0) goto get_created_failed; if (n_vms > 0) { GST_DEBUG ("Successfully got existing Java VM %p", java_vm); } else { JNIEnv *env; JavaVMInitArgs vm_args; JavaVMOption options[4]; GST_DEBUG ("Found no existing Java VM, trying to start one"); options[0].optionString = "-verbose:jni"; options[1].optionString = "-verbose:gc"; options[2].optionString = "-Xcheck:jni"; options[3].optionString = "-Xdebug"; vm_args.version = JNI_VERSION_1_4; vm_args.options = options; vm_args.nOptions = 4; vm_args.ignoreUnrecognized = JNI_TRUE; if (create_java_vm (&java_vm, &env, &vm_args) < 0) goto create_failed; GST_DEBUG ("Successfully created Java VM %p", java_vm); started_java_vm = TRUE; } return java_vm != NULL; get_created_failed: { GST_ERROR ("Failed to get already created VMs"); g_module_close (java_module); java_module = NULL; return FALSE; } create_failed: { GST_ERROR ("Failed to create a Java VM"); g_module_close (java_module); java_module = NULL; return FALSE; } } static void gst_amc_jni_set_error_string (JNIEnv * env, GQuark domain, gint code, GError ** err, const gchar * message) { jthrowable exception; if (!err) { if ((*env)->ExceptionCheck (env)) (*env)->ExceptionClear (env); return; } if ((*env)->ExceptionCheck (env)) { if ((exception = (*env)->ExceptionOccurred (env))) { gchar *exception_description, *exception_stacktrace; /* Clear exception so that we can call Java methods again */ (*env)->ExceptionClear (env); exception_description = getExceptionSummary (env, exception); exception_stacktrace = getStackTrace (env, exception); g_set_error (err, domain, code, "%s: %s\n%s", message, exception_description, exception_stacktrace); g_free (exception_description); g_free (exception_stacktrace); (*env)->DeleteLocalRef (env, exception); } else { (*env)->ExceptionClear (env); g_set_error (err, domain, code, "%s", message); } } else { g_set_error (err, domain, code, "%s", message); } } G_GNUC_PRINTF (5, 6) void gst_amc_jni_set_error (JNIEnv * env, GQuark domain, gint code, GError ** err, const gchar * format, ...) { gchar *message; va_list var_args; va_start (var_args, format); message = g_strdup_vprintf (format, var_args); va_end (var_args); gst_amc_jni_set_error_string (env, domain, code, err, message); g_free (message); } gboolean gst_amc_jni_initialize (void) { if (!initialized) { pthread_key_create (¤t_jni_env, gst_amc_jni_detach_current_thread); initialized = gst_amc_jni_initialize_java_vm (); } return initialized; } JNIEnv * gst_amc_jni_get_env (void) { JNIEnv *env; if ((env = pthread_getspecific (current_jni_env)) == NULL) { env = gst_amc_jni_attach_current_thread (); pthread_setspecific (current_jni_env, env); } return env; } gboolean gst_amc_jni_is_vm_started (void) { return started_java_vm; } #define CALL_STATIC_TYPE_METHOD(_type, _name, _jname, _retval) \ _type gst_amc_jni_call_static_##_name##_method (JNIEnv *env, GError ** err, jclass klass, jmethodID methodID, ...) \ { \ _type ret; \ va_list args; \ va_start(args, methodID); \ ret = (*env)->CallStatic##_jname##MethodV(env, klass, methodID, args); \ if ((*env)->ExceptionCheck (env)) { \ gst_amc_jni_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, \ err, "Failed to call static Java method"); \ (*env)->ExceptionClear (env); \ ret = _retval; \ } \ va_end(args); \ return (_type) ret; \ } CALL_STATIC_TYPE_METHOD (gboolean, boolean, Boolean, FALSE); CALL_STATIC_TYPE_METHOD (gint8, byte, Byte, G_MININT8); CALL_STATIC_TYPE_METHOD (gshort, short, Short, G_MINSHORT); CALL_STATIC_TYPE_METHOD (gint, int, Int, G_MININT); CALL_STATIC_TYPE_METHOD (gchar, char, Char, 0); CALL_STATIC_TYPE_METHOD (glong, long, Long, G_MINLONG); CALL_STATIC_TYPE_METHOD (gfloat, float, Float, G_MINFLOAT); CALL_STATIC_TYPE_METHOD (gdouble, double, Double, G_MINDOUBLE); CALL_STATIC_TYPE_METHOD (jobject, object, Object, NULL); gboolean gst_amc_jni_call_static_void_method (JNIEnv * env, GError ** err, jclass klass, jmethodID methodID, ...) { gboolean ret = TRUE; va_list args; va_start (args, methodID); (*env)->CallStaticVoidMethodV (env, klass, methodID, args); if ((*env)->ExceptionCheck (env)) { gst_amc_jni_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err, "Failed to call static Java method"); (*env)->ExceptionClear (env); ret = FALSE; } va_end (args); return ret; } #define CALL_TYPE_METHOD(_type, _name, _jname, _retval) \ _type gst_amc_jni_call_##_name##_method (JNIEnv *env, GError ** err, jobject obj, jmethodID methodID, ...) \ { \ _type ret; \ va_list args; \ va_start(args, methodID); \ ret = (*env)->Call##_jname##MethodV(env, obj, methodID, args); \ if ((*env)->ExceptionCheck (env)) { \ gst_amc_jni_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, \ err, "Failed to call Java method"); \ (*env)->ExceptionClear (env); \ ret = _retval; \ } \ va_end(args); \ return (_type) ret; \ } CALL_TYPE_METHOD (gboolean, boolean, Boolean, FALSE); CALL_TYPE_METHOD (gint8, byte, Byte, G_MININT8); CALL_TYPE_METHOD (gshort, short, Short, G_MINSHORT); CALL_TYPE_METHOD (gint, int, Int, G_MININT); CALL_TYPE_METHOD (gchar, char, Char, 0); CALL_TYPE_METHOD (glong, long, Long, G_MINLONG); CALL_TYPE_METHOD (gfloat, float, Float, G_MINFLOAT); CALL_TYPE_METHOD (gdouble, double, Double, G_MINDOUBLE); CALL_TYPE_METHOD (jobject, object, Object, NULL); gboolean gst_amc_jni_call_void_method (JNIEnv * env, GError ** err, jobject obj, jmethodID methodID, ...) { gboolean ret = TRUE; va_list args; va_start (args, methodID); (*env)->CallVoidMethodV (env, obj, methodID, args); if ((*env)->ExceptionCheck (env)) { gst_amc_jni_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, err, "Failed to call Java method"); (*env)->ExceptionClear (env); ret = FALSE; } va_end (args); return ret; } #define GET_TYPE_FIELD(_type, _name, _jname, _retval) \ _type gst_amc_jni_get_##_name##_field (JNIEnv *env, GError ** err, jobject obj, jfieldID fieldID) \ { \ _type res; \ \ res = (*env)->Get##_jname##Field(env, obj, fieldID); \ if ((*env)->ExceptionCheck (env)) { \ gst_amc_jni_set_error (env, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, \ err, "Failed to call Java field"); \ (*env)->ExceptionClear (env); \ res = _retval; \ } \ return res; \ } GET_TYPE_FIELD (gint, int, Int, G_MININT); GET_TYPE_FIELD (glong, long, Long, G_MINLONG);