/*
* Copyright 2018 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.
*/
#define LOG_TAG "JavaVMHelper"
#include "mediaplayer2/JavaVMHelper.h"
#include <media/stagefright/foundation/ADebug.h>
#include <utils/threads.h>
#include <stdlib.h>
namespace android {
// static
std::atomic<JavaVM *> JavaVMHelper::sJavaVM(NULL);
/*
* Makes the current thread visible to the VM.
*
* The JNIEnv pointer returned is only valid for the current thread, and
* thus must be tucked into thread-local storage.
*/
static int javaAttachThread(const char* threadName, JNIEnv** pEnv) {
JavaVMAttachArgs args;
JavaVM* vm;
jint result;
vm = JavaVMHelper::getJavaVM();
if (vm == NULL) {
return JNI_ERR;
}
args.version = JNI_VERSION_1_4;
args.name = (char*) threadName;
args.group = NULL;
result = vm->AttachCurrentThread(pEnv, (void*) &args);
if (result != JNI_OK) {
ALOGI("NOTE: attach of thread '%s' failed\n", threadName);
}
return result;
}
/*
* Detach the current thread from the set visible to the VM.
*/
static int javaDetachThread(void) {
JavaVM* vm;
jint result;
vm = JavaVMHelper::getJavaVM();
if (vm == NULL) {
return JNI_ERR;
}
result = vm->DetachCurrentThread();
if (result != JNI_OK) {
ALOGE("ERROR: thread detach failed\n");
}
return result;
}
/*
* When starting a native thread that will be visible from the VM, we
* bounce through this to get the right attach/detach action.
* Note that this function calls free(args)
*/
static int javaThreadShell(void* args) {
void* start = ((void**)args)[0];
void* userData = ((void **)args)[1];
char* name = (char*) ((void **)args)[2]; // we own this storage
free(args);
JNIEnv* env;
int result;
/* hook us into the VM */
if (javaAttachThread(name, &env) != JNI_OK) {
return -1;
}
/* start the thread running */
result = (*(android_thread_func_t)start)(userData);
/* unhook us */
javaDetachThread();
free(name);
return result;
}
/*
* This is invoked from androidCreateThreadEtc() via the callback
* set with androidSetCreateThreadFunc().
*
* We need to create the new thread in such a way that it gets hooked
* into the VM before it really starts executing.
*/
static int javaCreateThreadEtc(
android_thread_func_t entryFunction,
void* userData,
const char* threadName,
int32_t threadPriority,
size_t threadStackSize,
android_thread_id_t* threadId) {
void** args = (void**) malloc(3 * sizeof(void*)); // javaThreadShell must free
int result;
LOG_ALWAYS_FATAL_IF(threadName == nullptr, "threadName not provided to javaCreateThreadEtc");
args[0] = (void*) entryFunction;
args[1] = userData;
args[2] = (void*) strdup(threadName); // javaThreadShell must free
result = androidCreateRawThreadEtc(javaThreadShell, args,
threadName, threadPriority, threadStackSize, threadId);
return result;
}
// static
JNIEnv *JavaVMHelper::getJNIEnv() {
JNIEnv *env;
JavaVM *vm = sJavaVM.load();
CHECK(vm != NULL);
if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {
return NULL;
}
return env;
}
//static
JavaVM *JavaVMHelper::getJavaVM() {
return sJavaVM.load();
}
// static
void JavaVMHelper::setJavaVM(JavaVM *vm) {
sJavaVM.store(vm);
// Ensure that Thread(/*canCallJava*/ true) in libutils is attached to the VM.
// This is supposed to be done by runtime, but when libutils is used with linker
// namespace, CreateThreadFunc should be initialized separately within the namespace.
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
}
} // namespace android