1 /*
2 * Copyright 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "JavaVMHelper"
18
19 #include "mediaplayer2/JavaVMHelper.h"
20
21 #include <media/stagefright/foundation/ADebug.h>
22 #include <utils/threads.h>
23
24 #include <stdlib.h>
25
26 namespace android {
27
28 // static
29 std::atomic<JavaVM *> JavaVMHelper::sJavaVM(NULL);
30
31 /*
32 * Makes the current thread visible to the VM.
33 *
34 * The JNIEnv pointer returned is only valid for the current thread, and
35 * thus must be tucked into thread-local storage.
36 */
javaAttachThread(const char * threadName,JNIEnv ** pEnv)37 static int javaAttachThread(const char* threadName, JNIEnv** pEnv) {
38 JavaVMAttachArgs args;
39 JavaVM* vm;
40 jint result;
41
42 vm = JavaVMHelper::getJavaVM();
43 if (vm == NULL) {
44 return JNI_ERR;
45 }
46
47 args.version = JNI_VERSION_1_4;
48 args.name = (char*) threadName;
49 args.group = NULL;
50
51 result = vm->AttachCurrentThread(pEnv, (void*) &args);
52 if (result != JNI_OK) {
53 ALOGI("NOTE: attach of thread '%s' failed\n", threadName);
54 }
55
56 return result;
57 }
58
59 /*
60 * Detach the current thread from the set visible to the VM.
61 */
javaDetachThread(void)62 static int javaDetachThread(void) {
63 JavaVM* vm;
64 jint result;
65
66 vm = JavaVMHelper::getJavaVM();
67 if (vm == NULL) {
68 return JNI_ERR;
69 }
70
71 result = vm->DetachCurrentThread();
72 if (result != JNI_OK) {
73 ALOGE("ERROR: thread detach failed\n");
74 }
75 return result;
76 }
77
78 /*
79 * When starting a native thread that will be visible from the VM, we
80 * bounce through this to get the right attach/detach action.
81 * Note that this function calls free(args)
82 */
javaThreadShell(void * args)83 static int javaThreadShell(void* args) {
84 void* start = ((void**)args)[0];
85 void* userData = ((void **)args)[1];
86 char* name = (char*) ((void **)args)[2]; // we own this storage
87 free(args);
88 JNIEnv* env;
89 int result;
90
91 /* hook us into the VM */
92 if (javaAttachThread(name, &env) != JNI_OK) {
93 return -1;
94 }
95
96 /* start the thread running */
97 result = (*(android_thread_func_t)start)(userData);
98
99 /* unhook us */
100 javaDetachThread();
101 free(name);
102
103 return result;
104 }
105
106 /*
107 * This is invoked from androidCreateThreadEtc() via the callback
108 * set with androidSetCreateThreadFunc().
109 *
110 * We need to create the new thread in such a way that it gets hooked
111 * into the VM before it really starts executing.
112 */
javaCreateThreadEtc(android_thread_func_t entryFunction,void * userData,const char * threadName,int32_t threadPriority,size_t threadStackSize,android_thread_id_t * threadId)113 static int javaCreateThreadEtc(
114 android_thread_func_t entryFunction,
115 void* userData,
116 const char* threadName,
117 int32_t threadPriority,
118 size_t threadStackSize,
119 android_thread_id_t* threadId) {
120 void** args = (void**) malloc(3 * sizeof(void*)); // javaThreadShell must free
121 int result;
122
123 LOG_ALWAYS_FATAL_IF(threadName == nullptr, "threadName not provided to javaCreateThreadEtc");
124
125 args[0] = (void*) entryFunction;
126 args[1] = userData;
127 args[2] = (void*) strdup(threadName); // javaThreadShell must free
128
129 result = androidCreateRawThreadEtc(javaThreadShell, args,
130 threadName, threadPriority, threadStackSize, threadId);
131 return result;
132 }
133
134 // static
getJNIEnv()135 JNIEnv *JavaVMHelper::getJNIEnv() {
136 JNIEnv *env;
137 JavaVM *vm = sJavaVM.load();
138 CHECK(vm != NULL);
139
140 if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {
141 return NULL;
142 }
143
144 return env;
145 }
146
147 //static
getJavaVM()148 JavaVM *JavaVMHelper::getJavaVM() {
149 return sJavaVM.load();
150 }
151
152 // static
setJavaVM(JavaVM * vm)153 void JavaVMHelper::setJavaVM(JavaVM *vm) {
154 sJavaVM.store(vm);
155
156 // Ensure that Thread(/*canCallJava*/ true) in libutils is attached to the VM.
157 // This is supposed to be done by runtime, but when libutils is used with linker
158 // namespace, CreateThreadFunc should be initialized separately within the namespace.
159 androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
160 }
161
162 } // namespace android
163