| /* |
| * BRLTTY - A background process providing access to the console screen (when in |
| * text mode) for a blind person using a refreshable braille display. |
| * |
| * Copyright (C) 1995-2023 by The BRLTTY Developers. |
| * |
| * BRLTTY comes with ABSOLUTELY NO WARRANTY. |
| * |
| * This is free software, placed under the terms of the |
| * GNU Lesser General Public License, as published by the Free Software |
| * Foundation; either version 2.1 of the License, or (at your option) any |
| * later version. Please see the file LICENSE-LGPL for details. |
| * |
| * Web Page: http://brltty.app/ |
| * |
| * This software is maintained by Dave Mielke <dave@mielke.cc>. |
| */ |
| |
| #include "prologue.h" |
| |
| #include <string.h> |
| |
| #include "log.h" |
| #include "thread.h" |
| #include "system.h" |
| #include "system_java.h" |
| |
| static JavaVM *javaVirtualMachine = NULL; |
| |
| JNIEXPORT jint |
| JNI_OnLoad (JavaVM *vm, void *reserved) { |
| javaVirtualMachine = vm; |
| return JAVA_JNI_VERSION; |
| } |
| |
| JNIEXPORT void |
| JNI_OnUnload (JavaVM *vm, void *reserved) { |
| javaVirtualMachine = NULL; |
| } |
| |
| JavaVM * |
| getJavaInvocationInterface (void) { |
| return javaVirtualMachine; |
| } |
| |
| typedef struct { |
| JavaVM *virtualMachine; |
| JNIEnv *nativeInterface; |
| char *threadName; |
| } ThreadSpecificData; |
| |
| static THREAD_SPECIFIC_DATA_NEW(tsdJavaNativeThread) { |
| ThreadSpecificData *tsd; |
| |
| if ((tsd = malloc(sizeof(*tsd)))) { |
| memset(tsd, 0, sizeof(*tsd)); |
| return tsd; |
| } else { |
| logMallocError(); |
| } |
| |
| return NULL; |
| } |
| |
| static THREAD_SPECIFIC_DATA_DESTROY(tsdJavaNativeThread) { |
| ThreadSpecificData *tsd = data; |
| |
| { |
| JavaVM *vm = tsd->virtualMachine; |
| (*vm)->DetachCurrentThread(vm); |
| } |
| |
| logMessage(LOG_DEBUG, "thread detached from Java VM: %s", tsd->threadName); |
| free(tsd->threadName); |
| free(tsd); |
| } |
| |
| THREAD_SPECIFIC_DATA_CONTROL(tsdJavaNativeThread); |
| |
| static char * |
| getJavaThreadName (JNIEnv *env) { |
| char *name = NULL; |
| static jclass Thread_class = NULL; |
| |
| if (findJavaClass(env, &Thread_class, JAVA_OBJ_THREAD)) { |
| static jmethodID Thread_currentThread = 0; |
| |
| if (findJavaStaticMethod(env, &Thread_currentThread, Thread_class, "currentThread", |
| JAVA_SIG_METHOD(JAVA_SIG_THREAD, |
| ))) { |
| jobject thread = (*env)->CallStaticObjectMethod(env, Thread_class, Thread_currentThread); |
| |
| if (!clearJavaException(env, 1)) { |
| static jmethodID Thread_getName = 0; |
| |
| if (findJavaInstanceMethod(env, &Thread_getName, Thread_class, "getName", |
| JAVA_SIG_METHOD(JAVA_SIG_STRING, |
| ))) { |
| jstring jName = (*env)->CallObjectMethod(env, thread, Thread_getName); |
| |
| if (!clearJavaException(env, 1)) { |
| jboolean isCopy; |
| const char *cName = (*env)->GetStringUTFChars(env, jName, &isCopy); |
| |
| if (!(name = strdup(cName))) { |
| logMallocError(); |
| } |
| |
| (*env)->ReleaseStringUTFChars(env, jName, cName); |
| (*env)->DeleteLocalRef(env, jName); |
| } |
| } |
| |
| (*env)->DeleteLocalRef(env, thread); |
| } |
| } |
| } |
| |
| return name; |
| } |
| |
| JNIEnv * |
| getJavaNativeInterface (void) { |
| JavaVM *vm = getJavaInvocationInterface(); |
| |
| #ifdef __ANDROID__ |
| JNIEnv *env = NULL; |
| #else /* __ANDROID__ */ |
| void *env = NULL; |
| #endif /* __ANDROID__ */ |
| |
| if (vm) { |
| jint result = (*vm)->GetEnv(vm, (void **)&env, JAVA_JNI_VERSION); |
| |
| if (result != JNI_OK) { |
| if (result == JNI_EDETACHED) { |
| JavaVMAttachArgs args = { |
| .version = JAVA_JNI_VERSION, |
| .name = NULL, |
| .group = NULL |
| }; |
| |
| if ((result = (*vm)->AttachCurrentThread(vm, &env, &args)) < 0) { |
| logMessage(LOG_WARNING, "Java AttachCurrentThread error: %d", result); |
| } else { |
| ThreadSpecificData *tsd = getThreadSpecificData(&tsdJavaNativeThread); |
| tsd->virtualMachine = vm; |
| tsd->nativeInterface = env; |
| tsd->threadName = getJavaThreadName(env); |
| |
| logMessage(LOG_DEBUG, "thread attached to Java VM: %s", tsd->threadName); |
| } |
| } else { |
| logMessage(LOG_WARNING, "Java GetEnv error: %d", result); |
| } |
| } |
| } |
| |
| return env; |
| } |
| |
| int |
| clearJavaException (JNIEnv *env, int describe) { |
| int exceptionOccurred = (*env)->ExceptionCheck(env); |
| |
| if (exceptionOccurred) { |
| if (describe) (*env)->ExceptionDescribe(env); |
| (*env)->ExceptionClear(env); |
| } |
| |
| return exceptionOccurred; |
| } |
| |
| static jobject javaClassLoaderInstance = NULL; |
| static jclass javaClassLoaderClass = NULL; |
| static jmethodID loadClassMethod = 0; |
| |
| int |
| setJavaClassLoader (JNIEnv *env, jobject instance) { |
| if (instance) { |
| javaClassLoaderInstance = (*env)->NewGlobalRef(env, instance); |
| |
| if (javaClassLoaderInstance) { |
| jclass class = (*env)->GetObjectClass(env, instance); |
| |
| if (class) { |
| javaClassLoaderClass = (*env)->NewGlobalRef(env, class); |
| |
| (*env)->DeleteLocalRef(env, class); |
| class = NULL; |
| |
| if (javaClassLoaderClass) { |
| jmethodID method = (*env)->GetMethodID(env, javaClassLoaderClass, "loadClass", |
| JAVA_SIG_METHOD(JAVA_SIG_CLASS, |
| JAVA_SIG_STRING // className |
| )); |
| |
| if (method) { |
| loadClassMethod = method; |
| return 1; |
| } |
| |
| (*env)->DeleteGlobalRef(env, javaClassLoaderClass); |
| } |
| } |
| |
| (*env)->DeleteGlobalRef(env, javaClassLoaderInstance); |
| } |
| } |
| |
| javaClassLoaderInstance = NULL; |
| javaClassLoaderClass = NULL; |
| loadClassMethod = 0; |
| return 0; |
| } |
| |
| static jclass |
| loadJavaClass (JNIEnv *env, const char *path) { |
| size_t size = strlen(path) + 1; |
| char cName[size]; |
| |
| jclass class = NULL; |
| jobject jName; |
| |
| { |
| const char *p = path; |
| char *n = cName; |
| char c; |
| |
| do { |
| c = *p++; |
| if (c == '/') c = '.'; |
| *n++ = c; |
| } while (c); |
| } |
| |
| if ((jName = (*env)->NewStringUTF(env, cName))) { |
| jclass result = (*env)->CallObjectMethod(env, javaClassLoaderInstance, loadClassMethod, jName); |
| |
| if (clearJavaException(env, 1)) { |
| (*env)->DeleteLocalRef(env, result); |
| } else { |
| class = result; |
| } |
| |
| (*env)->DeleteLocalRef(env, jName); |
| } else { |
| logMallocError(); |
| } |
| |
| return class; |
| } |
| |
| int |
| findJavaClass (JNIEnv *env, jclass *class, const char *path) { |
| if (*class) return 1; |
| |
| { |
| jclass localReference = loadClassMethod? |
| loadJavaClass(env, path): |
| (*env)->FindClass(env, path); |
| |
| if (localReference) { |
| jclass globalReference = (*env)->NewGlobalRef(env, localReference); |
| |
| (*env)->DeleteLocalRef(env, localReference); |
| localReference = NULL; |
| |
| if (globalReference) { |
| logMessage(LOG_DEBUG, "java class found: %s", path); |
| *class = globalReference; |
| return 1; |
| } else { |
| logMallocError(); |
| clearJavaException(env, 0); |
| } |
| } else { |
| logMessage(LOG_ERR, "java class not found: %s", path); |
| clearJavaException(env, 1); |
| } |
| } |
| |
| return 0; |
| } |
| |
| int |
| findJavaInstanceMethod ( |
| JNIEnv *env, jmethodID *method, |
| jclass class, const char *name, const char *signature |
| ) { |
| if (!*method) { |
| if (!(*method = (*env)->GetMethodID(env, class, name, signature))) { |
| logMessage(LOG_ERR, "java instance method not found: %s: %s", name, signature); |
| clearJavaException(env, 0); |
| return 0; |
| } |
| |
| logMessage(LOG_DEBUG, "java instance method found: %s: %s", name, signature); |
| } |
| |
| return 1; |
| } |
| |
| int |
| findJavaStaticMethod ( |
| JNIEnv *env, jmethodID *method, |
| jclass class, const char *name, const char *signature |
| ) { |
| if (!*method) { |
| if (!(*method = (*env)->GetStaticMethodID(env, class, name, signature))) { |
| logMessage(LOG_ERR, "java static method not found: %s: %s", name, signature); |
| clearJavaException(env, 0); |
| return 0; |
| } |
| |
| logMessage(LOG_DEBUG, "java static method found: %s: %s", name, signature); |
| } |
| |
| return 1; |
| } |
| |
| int |
| findJavaConstructor ( |
| JNIEnv *env, jmethodID *constructor, |
| jclass class, const char *signature |
| ) { |
| return findJavaInstanceMethod(env, constructor, class, JAVA_CONSTRUCTOR_NAME, signature); |
| } |
| |
| int |
| findJavaInstanceField ( |
| JNIEnv *env, jfieldID *field, |
| jclass class, const char *name, const char *signature |
| ) { |
| if (!*field) { |
| if (!(*field = (*env)->GetFieldID(env, class, name, signature))) { |
| logMessage(LOG_ERR, "java instance field not found: %s: %s", name, signature); |
| clearJavaException(env, 0); |
| return 0; |
| } |
| |
| logMessage(LOG_DEBUG, "java instance field found: %s: %s", name, signature); |
| } |
| |
| return 1; |
| } |
| |
| int |
| findJavaStaticField ( |
| JNIEnv *env, jfieldID *field, |
| jclass class, const char *name, const char *signature |
| ) { |
| if (!*field) { |
| if (!(*field = (*env)->GetStaticFieldID(env, class, name, signature))) { |
| logMessage(LOG_ERR, "java static field not found: %s: %s", name, signature); |
| clearJavaException(env, 0); |
| return 0; |
| } |
| |
| logMessage(LOG_DEBUG, "java static field found: %s: %s", name, signature); |
| } |
| |
| return 1; |
| } |
| |
| char * |
| getJavaLocaleName (void) { |
| char *name = NULL; |
| JNIEnv *env; |
| |
| if ((env = getJavaNativeInterface())) { |
| jclass Locale_class = NULL; |
| |
| if (findJavaClass(env, &Locale_class, JAVA_OBJ_LOCALE)) { |
| jmethodID Locale_getDefault = 0; |
| |
| if (findJavaStaticMethod(env, &Locale_getDefault, Locale_class, "getDefault", |
| JAVA_SIG_METHOD(JAVA_SIG_LOCALE, |
| ))) { |
| jobject locale = (*env)->CallStaticObjectMethod(env, Locale_class, Locale_getDefault); |
| |
| if (!clearJavaException(env, 1)) { |
| jmethodID Locale_toString = 0; |
| |
| if (findJavaInstanceMethod(env, &Locale_toString, Locale_class, "toString", |
| JAVA_SIG_METHOD(JAVA_SIG_STRING, |
| ))) { |
| jstring jName = (*env)->CallObjectMethod(env, locale, Locale_toString); |
| |
| if (!clearJavaException(env, 1)) { |
| jboolean isCopy; |
| const char *cName = (*env)->GetStringUTFChars(env, jName, &isCopy); |
| |
| if (!(name = strdup(cName))) { |
| logMallocError(); |
| } |
| |
| (*env)->ReleaseStringUTFChars(env, jName, cName); |
| (*env)->DeleteLocalRef(env, jName); |
| } |
| } |
| |
| (*env)->DeleteLocalRef(env, locale); |
| } |
| } |
| } |
| } |
| |
| return name; |
| } |
| |
| #if defined(__ANDROID__) |
| #include <locale.h> |
| #include "messages.h" |
| |
| static void |
| initializeAndroidEnvironment (JNIEnv *env) { |
| { |
| static jclass class = NULL; |
| |
| if (findJavaClass(env, &class, JAVA_OBJ_BRLTTY("BrailleApplication"))) { |
| static jmethodID method = 0; |
| |
| if (findJavaStaticMethod(env, &method, class, "getCurrentLocale", |
| JAVA_SIG_METHOD(JAVA_SIG_STRING, |
| ))) { |
| jstring jLocale = (*env)->CallStaticObjectMethod(env, class, method); |
| |
| if (!clearJavaException(env, 1)) { |
| if (jLocale) { |
| jboolean isCopy; |
| const char *cLocale = (*env)->GetStringUTFChars(env, jLocale, &isCopy); |
| |
| if (cLocale) { |
| if (setMessagesLocale(cLocale)) { |
| } |
| |
| (*env)->ReleaseStringUTFChars(env, jLocale, cLocale); |
| } |
| |
| (*env)->DeleteLocalRef(env, jLocale); |
| } |
| } |
| } |
| } |
| } |
| } |
| #endif /* platform-speciofic initialization */ |
| |
| void |
| initializeSystemObject (void) { |
| #if defined(__ANDROID__) |
| initializeAndroidEnvironment(getJavaNativeInterface()); |
| #endif /* platform-speciofic initialization */ |
| } |