Merge "RpcSession attaches/detaches thread on JVM (second attempt)"
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index da570f3..cb65bef 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -192,6 +192,7 @@
 
     header_libs: [
         "libbinder_headers",
+        "libandroid_runtime_vm_headers",
     ],
 
     export_header_lib_headers: [
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index f637804..c01a03d 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -18,16 +18,20 @@
 
 #include <binder/RpcSession.h>
 
+#include <dlfcn.h>
 #include <inttypes.h>
 #include <poll.h>
+#include <pthread.h>
 #include <unistd.h>
 
 #include <string_view>
 
 #include <android-base/macros.h>
+#include <android_runtime/vm.h>
 #include <binder/Parcel.h>
 #include <binder/RpcServer.h>
 #include <binder/Stability.h>
+#include <jni.h>
 #include <utils/String8.h>
 
 #include "RpcSocketAddress.h"
@@ -297,10 +301,66 @@
     };
 }
 
+namespace {
+// RAII object for attaching / detaching current thread to JVM if Android Runtime exists. If
+// Android Runtime doesn't exist, no-op.
+class JavaThreadAttacher {
+public:
+    JavaThreadAttacher() {
+        // Use dlsym to find androidJavaAttachThread because libandroid_runtime is loaded after
+        // libbinder.
+        auto vm = getJavaVM();
+        if (vm == nullptr) return;
+
+        char threadName[16];
+        if (0 != pthread_getname_np(pthread_self(), threadName, sizeof(threadName))) {
+            constexpr const char* defaultThreadName = "UnknownRpcSessionThread";
+            memcpy(threadName, defaultThreadName,
+                   std::min<size_t>(sizeof(threadName), strlen(defaultThreadName) + 1));
+        }
+        LOG_RPC_DETAIL("Attaching current thread %s to JVM", threadName);
+        JavaVMAttachArgs args;
+        args.version = JNI_VERSION_1_2;
+        args.name = threadName;
+        args.group = nullptr;
+        JNIEnv* env;
+
+        LOG_ALWAYS_FATAL_IF(vm->AttachCurrentThread(&env, &args) != JNI_OK,
+                            "Cannot attach thread %s to JVM", threadName);
+        mAttached = true;
+    }
+    ~JavaThreadAttacher() {
+        if (!mAttached) return;
+        auto vm = getJavaVM();
+        LOG_ALWAYS_FATAL_IF(vm == nullptr,
+                            "Unable to detach thread. No JavaVM, but it was present before!");
+
+        LOG_RPC_DETAIL("Detaching current thread from JVM");
+        if (vm->DetachCurrentThread() != JNI_OK) {
+            mAttached = false;
+        } else {
+            ALOGW("Unable to detach current thread from JVM");
+        }
+    }
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(JavaThreadAttacher);
+    bool mAttached = false;
+
+    static JavaVM* getJavaVM() {
+        static auto fn = reinterpret_cast<decltype(&AndroidRuntimeGetJavaVM)>(
+                dlsym(RTLD_DEFAULT, "AndroidRuntimeGetJavaVM"));
+        if (fn == nullptr) return nullptr;
+        return fn();
+    }
+};
+} // namespace
+
 void RpcSession::join(sp<RpcSession>&& session, PreJoinSetupResult&& setupResult) {
     sp<RpcConnection>& connection = setupResult.connection;
 
     if (setupResult.status == OK) {
+        JavaThreadAttacher javaThreadAttacher;
         while (true) {
             status_t status = session->state()->getAndExecuteCommand(connection, session,
                                                                      RpcState::CommandType::ANY);
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index f5aea43..40ebd9c 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -1218,6 +1218,43 @@
             << "After server->shutdown() returns true, join() did not stop after 2s";
 }
 
+TEST(BinderRpc, Java) {
+#if !defined(__ANDROID__)
+    GTEST_SKIP() << "This test is only run on Android. Though it can technically run on host on"
+                    "createRpcDelegateServiceManager() with a device attached, such test belongs "
+                    "to binderHostDeviceTest. Hence, just disable this test on host.";
+#endif // !__ANDROID__
+    sp<IServiceManager> sm = defaultServiceManager();
+    ASSERT_NE(nullptr, sm);
+    // Any Java service with non-empty getInterfaceDescriptor() would do.
+    // Let's pick batteryproperties.
+    auto binder = sm->checkService(String16("batteryproperties"));
+    ASSERT_NE(nullptr, binder);
+    auto descriptor = binder->getInterfaceDescriptor();
+    ASSERT_GE(descriptor.size(), 0);
+    ASSERT_EQ(OK, binder->pingBinder());
+
+    auto rpcServer = RpcServer::make();
+    rpcServer->iUnderstandThisCodeIsExperimentalAndIWillNotUseItInProduction();
+    unsigned int port;
+    ASSERT_TRUE(rpcServer->setupInetServer(0, &port));
+    auto socket = rpcServer->releaseServer();
+
+    auto keepAlive = sp<BBinder>::make();
+    ASSERT_EQ(OK, binder->setRpcClientDebug(std::move(socket), keepAlive));
+
+    auto rpcSession = RpcSession::make();
+    ASSERT_TRUE(rpcSession->setupInetClient("127.0.0.1", port));
+    auto rpcBinder = rpcSession->getRootObject();
+    ASSERT_NE(nullptr, rpcBinder);
+
+    ASSERT_EQ(OK, rpcBinder->pingBinder());
+
+    ASSERT_EQ(descriptor, rpcBinder->getInterfaceDescriptor())
+            << "getInterfaceDescriptor should not crash system_server";
+    ASSERT_EQ(OK, rpcBinder->pingBinder());
+}
+
 } // namespace android
 
 int main(int argc, char** argv) {