Ensure that Thread(/*canCallJava*/ true) is attached to the VM.

This is supposed to be done by runtime, but when libutils is
dual-loaded with linker namespace, CreateThreadFunc should be
initialized separately within the namespace since libutils keeps
it in a global variable.

Test: build & boot & atest MediaPlayer2Test
Bug: 112766913
Change-Id: I23eac262c8b88178bf151d7c6ab49a48af932001
diff --git a/media/libmediaplayer2/JavaVMHelper.cpp b/media/libmediaplayer2/JavaVMHelper.cpp
index 90aaa7f..8d03ed0 100644
--- a/media/libmediaplayer2/JavaVMHelper.cpp
+++ b/media/libmediaplayer2/JavaVMHelper.cpp
@@ -19,6 +19,7 @@
 #include "mediaplayer2/JavaVMHelper.h"
 
 #include <media/stagefright/foundation/ADebug.h>
+#include <utils/threads.h>
 
 #include <stdlib.h>
 
@@ -27,6 +28,109 @@
 // 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;
@@ -40,9 +144,19 @@
     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