The open source OpenXR runtime
1// Copyright 2020, Collabora, Ltd.
2// Copyright 2024-2025, NVIDIA CORPORATION.
3// SPDX-License-Identifier: BSL-1.0
4/*!
5 * @file
6 * @brief Library exposing IPC server.
7 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
8 * @ingroup ipc_android
9 */
10
11#include "jnipp.h"
12#include "jni.h"
13
14#include "wrap/android.view.h"
15
16#include "server/ipc_server.h"
17#include "server/ipc_server_interface.h"
18#include "server/ipc_server_mainloop_android.h"
19#include "util/u_logging.h"
20
21#include <android/native_window.h>
22#include <android/native_window_jni.h>
23
24#include "android/android_globals.h"
25
26#include <chrono>
27#include <memory>
28#include <thread>
29
30using wrap::android::view::Surface;
31using namespace std::chrono_literals;
32
33namespace {
34struct IpcServerHelper
35{
36public:
37 static IpcServerHelper &
38 instance()
39 {
40 static IpcServerHelper instance;
41 return instance;
42 }
43
44 void
45 signalStartupComplete(ipc_server *s)
46 {
47 std::unique_lock<std::mutex> lock{server_mutex};
48 startup_complete = true;
49 server = s;
50 startup_cond.notify_all();
51 }
52
53 void
54 startServer()
55 {
56 std::unique_lock lock(server_mutex);
57 if (!server && !server_thread) {
58 server_thread =
59 std::make_unique<std::thread>([&]() { ipc_server_main_common(&ismi, &callbacks, this); });
60 }
61 }
62
63 static void
64 signalInitFailed(xrt_result_t xret, void *data)
65 {
66 static_cast<IpcServerHelper *>(data)->signalStartupComplete(nullptr);
67 }
68
69 static void
70 signalStartupCompleteTrampoline(ipc_server *s, xrt_instance *xsint, void *data)
71 {
72 static_cast<IpcServerHelper *>(data)->signalStartupComplete(s);
73 }
74
75 static void
76 signalShuttingDownTrampoline(ipc_server *s, xrt_instance *xsint, void *data)
77 {
78 // No-op
79 }
80
81 static void
82 signalClientConnectedTrampoline(struct ipc_server *s, uint32_t client_id, void *data)
83 {
84 // No-op
85 }
86
87 static void
88 signalClientDisconnectedTrampoline(struct ipc_server *s, uint32_t client_id, void *data)
89 {
90 // No-op
91 }
92
93 int32_t
94 addClient(int fd)
95 {
96 if (!waitForStartupComplete()) {
97 return -1;
98 }
99 return ipc_server_mainloop_add_fd(server, &server->ml, fd);
100 }
101
102 int32_t
103 shutdownServer()
104 {
105 if (!server || !server_thread) {
106 // Should not happen.
107 U_LOG_E("service: shutdownServer called before server started up!");
108 return -1;
109 }
110
111 {
112 // Wait until IPC server stop
113 std::unique_lock lock(server_mutex);
114 ipc_server_handle_shutdown_signal(server);
115 server_thread->join();
116 server_thread.reset(nullptr);
117 server = NULL;
118 startup_complete = false;
119 }
120
121 return 0;
122 }
123
124private:
125 IpcServerHelper() {}
126
127 bool
128 waitForStartupComplete()
129 {
130 std::unique_lock<std::mutex> lock{server_mutex};
131 bool completed = startup_cond.wait_for(lock, START_TIMEOUT_SECONDS, [&]() { return startup_complete; });
132
133 if (!server) {
134 U_LOG_E("Failed to create ipc server");
135 }
136
137 if (!completed) {
138 U_LOG_E("Server startup timeout!");
139 }
140 return server && completed;
141 }
142
143 const struct ipc_server_main_info ismi = {
144 .udgci =
145 {
146 .window_title = "Monado Android Service",
147 .open = U_DEBUG_GUI_OPEN_NEVER,
148 },
149 };
150
151 const struct ipc_server_callbacks callbacks = {
152 .init_failed = signalInitFailed,
153 .mainloop_entering = signalStartupCompleteTrampoline,
154 .mainloop_leaving = signalShuttingDownTrampoline,
155 .client_connected = signalClientConnectedTrampoline,
156 .client_disconnected = signalClientDisconnectedTrampoline,
157 };
158
159 //! Reference to the ipc_server, managed by ipc_server_process
160 struct ipc_server *server = NULL;
161
162 //! Mutex for starting thread
163 std::mutex server_mutex;
164
165 //! Server thread
166 std::unique_ptr<std::thread> server_thread{};
167
168 //! Condition variable for starting thread
169 std::condition_variable startup_cond;
170
171 //! Server startup state
172 bool startup_complete = false;
173
174 //! Timeout duration in seconds
175 static constexpr std::chrono::seconds START_TIMEOUT_SECONDS = 40s;
176};
177} // namespace
178
179extern "C" JNIEXPORT void JNICALL
180Java_org_freedesktop_monado_ipc_MonadoImpl_nativeStartServer(JNIEnv *env, jobject thiz, jobject context)
181{
182 JavaVM *jvm = nullptr;
183 jint result = env->GetJavaVM(&jvm);
184 assert(result == JNI_OK);
185 assert(jvm);
186
187 jni::init(env);
188 jni::Object monadoImpl(thiz);
189 U_LOG_D("service: Called nativeStartServer");
190
191 android_globals_store_vm_and_context(jvm, context);
192
193 IpcServerHelper::instance().startServer();
194}
195
196extern "C" JNIEXPORT jint JNICALL
197Java_org_freedesktop_monado_ipc_MonadoImpl_nativeAddClient(JNIEnv *env, jobject thiz, int fd)
198{
199 jni::init(env);
200 jni::Object monadoImpl(thiz);
201 U_LOG_D("service: Called nativeAddClient with fd %d", fd);
202
203 int native_fd = dup(fd);
204 U_LOG_D("service: transfer ownership to native and native_fd %d", native_fd);
205
206 // We try pushing the fd number to the server. If and only if we get a 0 return, has the server taken ownership.
207 return IpcServerHelper::instance().addClient(native_fd);
208}
209
210extern "C" JNIEXPORT void JNICALL
211Java_org_freedesktop_monado_ipc_MonadoImpl_nativeAppSurface(JNIEnv *env, jobject thiz, jobject surface)
212{
213 jni::init(env);
214 Surface surf(surface);
215 jni::Object monadoImpl(thiz);
216
217 ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
218 android_globals_store_window((struct _ANativeWindow *)nativeWindow);
219 U_LOG_D("Stored ANativeWindow: %p", (void *)nativeWindow);
220}
221
222extern "C" JNIEXPORT jint JNICALL
223Java_org_freedesktop_monado_ipc_MonadoImpl_nativeShutdownServer(JNIEnv *env, jobject thiz)
224{
225 jni::init(env);
226 jni::Object monadoImpl(thiz);
227
228 return IpcServerHelper::instance().shutdownServer();
229}