/* * Copyright (C)2005-2018 Haxe Foundation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #if defined(HL_MOBILE) && defined(HL_ANDROID) #ifndef HL_ANDROID_ACTIVITY # define HL_ANDROID_ACTIVITY "org/haxe/HashLinkActivity" #endif #include #include #include #include #include #ifndef HL_JNI_LOG_TAG #define HL_JNI_LOG_TAG "HL_JNI" #endif #define HL_ANDROID_EXTERNAL_STORAGE_NOT_MOUNTED (0) #define HL_ANDROID_EXTERNAL_STORAGE_MOUNTED_RO (1) #define HL_ANDROID_EXTERNAL_STORAGE_MOUNTED_RW (2 | HL_ANDROID_EXTERNAL_STORAGE_MOUNTED_RO) /* #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,HL_JNI_LOG_TAG,__VA_ARGS__) */ /* #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,HL_JNI_LOG_TAG,__VA_ARGS__) */ #define LOGI(...) do {} while (0) #define LOGE(...) do {} while (0) /* Forward declaration for JNI thread management */ static void hl_android_jni_thread_destructor(void*); /* pthread key for proper JVM thread handling */ static pthread_key_t hl_java_thread_key; /* Java VM reference */ static JavaVM* hl_java_vm; /* Main activity */ static jclass hl_java_activity_class; /* Method signatures */ static jmethodID hl_java_method_id_get_context; /* Paths */ static char *hl_android_external_files_path = NULL; static char *hl_android_internal_files_path = NULL; /* Function to retrieve JNI environment, and dealing with threading */ static JNIEnv* hl_android_jni_get_env(void) { /* Always try to attach if calling from a non-attached thread */ JNIEnv *env; if((*hl_java_vm)->AttachCurrentThread(hl_java_vm, &env, NULL) < 0) { LOGE("failed to attach current thread"); return 0; } pthread_setspecific(hl_java_thread_key, (void*) env); return env; } /* JNI_OnLoad is automatically called when loading shared library through System.loadLibrary() Java call */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv *env; jclass cls; hl_java_vm = vm; if ((*hl_java_vm)->GetEnv(hl_java_vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { __android_log_print(ANDROID_LOG_ERROR, HL_JNI_LOG_TAG, "Failed to get the environment using GetEnv()"); return -1; } /* Create pthread "destructor" pthread key to detach properly all threads */ if (pthread_key_create(&hl_java_thread_key, hl_android_jni_thread_destructor) != 0) { __android_log_print(ANDROID_LOG_ERROR, HL_JNI_LOG_TAG, "Error initializing pthread key"); } /* Make sure we are attached (we should) and setup pthread destructor */ env = hl_android_jni_get_env(); /* Try to retrieve local reference to our Activity class */ cls = (*env)->FindClass(env, HL_ANDROID_ACTIVITY); if (!cls) { __android_log_print(ANDROID_LOG_ERROR, HL_JNI_LOG_TAG, "Error cannot find HashLink Activity class"); } /* Create a global reference for our Activity class */ hl_java_activity_class = (jclass)((*env)->NewGlobalRef(env, cls)); /* Retrieve the getContext() method id */ hl_java_method_id_get_context = (*env)->GetStaticMethodID(env, hl_java_activity_class, "getContext","()Landroid/content/Context;"); if (!hl_java_method_id_get_context) { __android_log_print(ANDROID_LOG_ERROR, HL_JNI_LOG_TAG, "Error cannot get getContext() method on specified Activity class (not an Activity ?)"); } return JNI_VERSION_1_4; } static void hl_android_jni_thread_destructor(void* value) { /* The thread is being destroyed, detach it from the Java VM and set the hl_java_thread_key value to NULL as required */ JNIEnv *env = (JNIEnv*) value; if (env != NULL) { (*hl_java_vm)->DetachCurrentThread(hl_java_vm); pthread_setspecific(hl_java_thread_key, NULL); } } static int hl_sys_android_get_external_storage_state(void) { jmethodID mid; jclass cls; jstring stateString; const char *state; int status = HL_ANDROID_EXTERNAL_STORAGE_NOT_MOUNTED; JNIEnv *env = hl_android_jni_get_env(); if (!env) { LOGE("Couldn't get Android JNIEnv !"); return status; } cls = (*env)->FindClass(env, "android/os/Environment"); mid = (*env)->GetStaticMethodID(env, cls, "getExternalStorageState", "()Ljava/lang/String;"); stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid); if (!stateString) { LOGE("Call to getExternalStorageState failed"); return status; } state = (*env)->GetStringUTFChars(env, stateString, NULL); if (strcmp(state, "mounted") == 0) { status = HL_ANDROID_EXTERNAL_STORAGE_MOUNTED_RW; } else if (strcmp(state, "mounted_ro") == 0) { status = HL_ANDROID_EXTERNAL_STORAGE_MOUNTED_RO; } (*env)->ReleaseStringUTFChars(env, stateString, state); return status; } static char* hl_sys_android_get_absolute_path_from(const char* method, const char* signature) { jmethodID mid; jobject context; jobject fileObject; jstring pathString; const char *path; char* retrievedPath = NULL; JNIEnv *env = hl_android_jni_get_env(); if (!env) { LOGE("Couldn't get Android JNIEnv !"); return NULL; } context = (*env)->CallStaticObjectMethod(env, hl_java_activity_class, hl_java_method_id_get_context); if (!context) { LOGE("Couldn't get Android context!"); return NULL; } mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), method, signature); fileObject = (*env)->CallObjectMethod(env, context, mid, NULL); if (!fileObject) { LOGE("Couldn't call %s%s on the specified context", method, signature); return NULL; } mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject), "getAbsolutePath", "()Ljava/lang/String;"); pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid); if (!pathString) { LOGE("Couldn't retrieve absolute path"); return NULL; } /* Retrieve as C string */ path = (*env)->GetStringUTFChars(env, pathString, NULL); retrievedPath = strdup(path); (*env)->ReleaseStringUTFChars(env, pathString, path); return retrievedPath; } static const char* hl_sys_android_get_external_storage_path(void) { /* Make sure external storage is mounted (at least read-only) */ if (hl_sys_android_get_external_storage_state()==HL_ANDROID_EXTERNAL_STORAGE_NOT_MOUNTED) return NULL; if (!hl_android_external_files_path) { hl_android_external_files_path = hl_sys_android_get_absolute_path_from("getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;"); } return hl_android_external_files_path; } static const char* hl_sys_android_get_internal_storage_path(void) { /* Internal storage is always available */ if (!hl_android_internal_files_path) { hl_android_internal_files_path = hl_sys_android_get_absolute_path_from("getFilesDir", "(Ljava/lang/String;)Ljava/io/File;"); } return hl_android_internal_files_path; } const char *hl_sys_special( const char *key ) { if (strcmp(key, "android_external_storage_path")==0) return hl_sys_android_get_external_storage_path(); else if (strcmp(key, "android_internal_storage_path")==0) return hl_sys_android_get_internal_storage_path(); else hl_error("Unknown sys_special key"); return NULL; } DEFINE_PRIM(_BYTES, sys_special, _BYTES); #endif