Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/openxr/src/loader/android_utilities.cpp
9917 views
1
// Copyright (c) 2020-2025 The Khronos Group Inc.
2
// Copyright (c) 2020-2021, Collabora, Ltd.
3
//
4
// SPDX-License-Identifier: Apache-2.0 OR MIT
5
//
6
// Initial Author: Rylie Pavlik <[email protected]>
7
8
#include "android_utilities.h"
9
10
#ifdef __ANDROID__
11
#include <wrap/android.net.h>
12
#include <wrap/android.content.h>
13
#include <wrap/android.database.h>
14
#include <json/value.h>
15
16
#include <openxr/openxr.h>
17
18
#include <dlfcn.h>
19
#include <sstream>
20
#include <vector>
21
#include <android/log.h>
22
23
#define LOG_TAG "OpenXR-Loader"
24
#include "android_logging.h"
25
26
namespace openxr_android {
27
using wrap::android::content::ContentUris;
28
using wrap::android::content::Context;
29
using wrap::android::database::Cursor;
30
using wrap::android::net::Uri;
31
using wrap::android::net::Uri_Builder;
32
33
// Code in here corresponds roughly to the Java "BrokerContract" class and subclasses.
34
namespace {
35
constexpr auto AUTHORITY = "org.khronos.openxr.runtime_broker";
36
constexpr auto SYSTEM_AUTHORITY = "org.khronos.openxr.system_runtime_broker";
37
constexpr auto BASE_PATH = "openxr";
38
constexpr auto ABI_PATH = "abi";
39
constexpr auto RUNTIMES_PATH = "runtimes";
40
41
constexpr const char *getBrokerAuthority(bool systemBroker) { return systemBroker ? SYSTEM_AUTHORITY : AUTHORITY; }
42
43
struct BaseColumns {
44
/**
45
* The unique ID for a row.
46
*/
47
[[maybe_unused]] static constexpr auto ID = "_id";
48
};
49
50
/**
51
* Contains details for the /openxr/[major_ver]/abi/[abi]/runtimes/active URI.
52
* <p>
53
* This URI represents a "table" containing at most one item, the currently active runtime. The
54
* policy of which runtime is chosen to be active (if more than one is installed) is left to the
55
* content provider.
56
* <p>
57
* No sort order is required to be honored by the content provider.
58
*/
59
namespace active_runtime {
60
/**
61
* Final path component to this URI.
62
*/
63
static constexpr auto TABLE_PATH = "active";
64
65
/**
66
* Create a content URI for querying the data on the active runtime for a
67
* given major version of OpenXR.
68
*
69
* @param systemBroker If the system runtime broker (instead of the installable one) should be queried.
70
* @param majorVer The major version of OpenXR.
71
* @param abi The Android ABI name in use.
72
* @return A content URI for a single item: the active runtime.
73
*/
74
static Uri makeContentUri(bool systemBroker, int majorVersion, const char *abi) {
75
auto builder = Uri_Builder::construct();
76
builder.scheme("content")
77
.authority(getBrokerAuthority(systemBroker))
78
.appendPath(BASE_PATH)
79
.appendPath(std::to_string(majorVersion))
80
.appendPath(ABI_PATH)
81
.appendPath(abi)
82
.appendPath(RUNTIMES_PATH)
83
.appendPath(TABLE_PATH);
84
return builder.build();
85
}
86
87
struct Columns : BaseColumns {
88
/**
89
* Constant for the PACKAGE_NAME column name
90
*/
91
static constexpr auto PACKAGE_NAME = "package_name";
92
93
/**
94
* Constant for the NATIVE_LIB_DIR column name
95
*/
96
static constexpr auto NATIVE_LIB_DIR = "native_lib_dir";
97
98
/**
99
* Constant for the SO_FILENAME column name
100
*/
101
static constexpr auto SO_FILENAME = "so_filename";
102
103
/**
104
* Constant for the HAS_FUNCTIONS column name.
105
* <p>
106
* If this column contains true, you should check the /functions/ URI for that runtime.
107
*/
108
static constexpr auto HAS_FUNCTIONS = "has_functions";
109
};
110
} // namespace active_runtime
111
112
/**
113
* Contains details for the /openxr/[major_ver]/abi/[abi]/runtimes/[package]/functions URI.
114
* <p>
115
* This URI is for package-specific function name remapping. Since this is an optional field in
116
* the corresponding JSON manifests for OpenXR, it is optional here as well. If the active
117
* runtime contains "true" in its "has_functions" column, then this table must exist and be
118
* queryable.
119
* <p>
120
* No sort order is required to be honored by the content provider.
121
*/
122
namespace functions {
123
/**
124
* Final path component to this URI.
125
*/
126
static constexpr auto TABLE_PATH = "functions";
127
128
/**
129
* Create a content URI for querying all rows of the function remapping data for a given
130
* runtime package and major version of OpenXR.
131
*
132
* @param systemBroker If the system runtime broker (instead of the installable one) should be queried.
133
* @param majorVer The major version of OpenXR.
134
* @param packageName The package name of the runtime.
135
* @param abi The Android ABI name in use.
136
* @return A content URI for the entire table: the function remapping for that runtime.
137
*/
138
static Uri makeContentUri(bool systemBroker, int majorVersion, std::string const &packageName, const char *abi) {
139
auto builder = Uri_Builder::construct();
140
builder.scheme("content")
141
.authority(getBrokerAuthority(systemBroker))
142
.appendPath(BASE_PATH)
143
.appendPath(std::to_string(majorVersion))
144
.appendPath(ABI_PATH)
145
.appendPath(abi)
146
.appendPath(RUNTIMES_PATH)
147
.appendPath(packageName)
148
.appendPath(TABLE_PATH);
149
return builder.build();
150
}
151
152
struct Columns : BaseColumns {
153
/**
154
* Constant for the FUNCTION_NAME column name
155
*/
156
static constexpr auto FUNCTION_NAME = "function_name";
157
158
/**
159
* Constant for the SYMBOL_NAME column name
160
*/
161
static constexpr auto SYMBOL_NAME = "symbol_name";
162
};
163
} // namespace functions
164
165
} // namespace
166
167
static inline jni::Array<std::string> makeArray(std::initializer_list<const char *> &&list) {
168
auto ret = jni::Array<std::string>{(long)list.size()};
169
long i = 0;
170
for (auto &&elt : list) {
171
ret.setElement(i, elt);
172
++i;
173
}
174
return ret;
175
}
176
177
#if defined(__arm__)
178
static constexpr auto ABI = "armeabi-v7l";
179
#elif defined(__aarch64__)
180
static constexpr auto ABI = "arm64-v8a";
181
#elif defined(__i386__)
182
static constexpr auto ABI = "x86";
183
#elif defined(__x86_64__)
184
static constexpr auto ABI = "x86_64";
185
#else
186
#error "Unknown ABI!"
187
#endif
188
189
/// Helper class to generate the jsoncpp object corresponding to a synthetic runtime manifest.
190
class JsonManifestBuilder {
191
public:
192
JsonManifestBuilder(const std::string &libraryPathParent, const std::string &libraryPath);
193
JsonManifestBuilder &function(const std::string &functionName, const std::string &symbolName);
194
195
Json::Value build() const { return root_node; }
196
197
private:
198
Json::Value root_node;
199
};
200
201
inline JsonManifestBuilder::JsonManifestBuilder(const std::string &libraryPathParent, const std::string &libraryPath)
202
: root_node(Json::objectValue) {
203
root_node["file_format_version"] = "1.0.0";
204
root_node["instance_extensions"] = Json::Value(Json::arrayValue);
205
root_node["functions"] = Json::Value(Json::objectValue);
206
root_node[libraryPathParent] = Json::objectValue;
207
root_node[libraryPathParent]["library_path"] = libraryPath;
208
}
209
210
inline JsonManifestBuilder &JsonManifestBuilder::function(const std::string &functionName, const std::string &symbolName) {
211
root_node["functions"][functionName] = symbolName;
212
return *this;
213
}
214
215
static constexpr const char *getBrokerTypeName(bool systemBroker) { return systemBroker ? "system" : "installable"; }
216
217
static int populateFunctions(wrap::android::content::Context const &context, bool systemBroker, const std::string &packageName,
218
JsonManifestBuilder &builder) {
219
jni::Array<std::string> projection = makeArray({functions::Columns::FUNCTION_NAME, functions::Columns::SYMBOL_NAME});
220
221
auto uri = functions::makeContentUri(systemBroker, XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), packageName, ABI);
222
ALOGI("populateFunctions: Querying URI: %s", uri.toString().c_str());
223
224
Cursor cursor = context.getContentResolver().query(uri, projection);
225
226
if (cursor.isNull()) {
227
ALOGE("Null cursor when querying content resolver for functions.");
228
return -1;
229
}
230
if (cursor.getCount() < 1) {
231
ALOGE("Non-null but empty cursor when querying content resolver for functions.");
232
cursor.close();
233
return -1;
234
}
235
auto functionIndex = cursor.getColumnIndex(functions::Columns::FUNCTION_NAME);
236
auto symbolIndex = cursor.getColumnIndex(functions::Columns::SYMBOL_NAME);
237
while (cursor.moveToNext()) {
238
builder.function(cursor.getString(functionIndex), cursor.getString(symbolIndex));
239
}
240
241
cursor.close();
242
return 0;
243
}
244
245
// The current file relies on android-jni-wrappers and jnipp, which may throw on failure.
246
// This is problematic when the loader is compiled with exception handling disabled - the consumers can reasonably
247
// expect that the compilation with -fno-exceptions will succeed, but the compiler will not accept the code that
248
// uses `try` & `catch` keywords. We cannot use the `exception_handling.hpp` here since we're not at an ABI boundary,
249
// so we define helper macros here. This is fine for now since the only occurrence of exception-handling code is in this file.
250
#ifdef XRLOADER_DISABLE_EXCEPTION_HANDLING
251
252
#define ANDROID_UTILITIES_TRY
253
#define ANDROID_UTILITIES_CATCH_FALLBACK(...)
254
255
#else
256
257
#define ANDROID_UTILITIES_TRY try
258
#define ANDROID_UTILITIES_CATCH_FALLBACK(...) \
259
catch (const std::exception &e) { \
260
__VA_ARGS__ \
261
}
262
263
#endif // XRLOADER_DISABLE_EXCEPTION_HANDLING
264
265
/// Get cursor for active runtime, parameterized by whether or not we use the system broker
266
static bool getActiveRuntimeCursor(wrap::android::content::Context const &context, jni::Array<std::string> const &projection,
267
bool systemBroker, Cursor &cursor) {
268
auto uri = active_runtime::makeContentUri(systemBroker, XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), ABI);
269
ALOGI("getActiveRuntimeCursor: Querying URI: %s", uri.toString().c_str());
270
271
ANDROID_UTILITIES_TRY { cursor = context.getContentResolver().query(uri, projection); }
272
ANDROID_UTILITIES_CATCH_FALLBACK({
273
ALOGW("Exception when querying %s content resolver: %s", getBrokerTypeName(systemBroker), e.what());
274
cursor = {};
275
return false;
276
})
277
278
if (cursor.isNull()) {
279
ALOGW("Null cursor when querying %s content resolver.", getBrokerTypeName(systemBroker));
280
cursor = {};
281
return false;
282
}
283
if (cursor.getCount() < 1) {
284
ALOGW("Non-null but empty cursor when querying %s content resolver.", getBrokerTypeName(systemBroker));
285
cursor.close();
286
cursor = {};
287
return false;
288
}
289
return true;
290
}
291
292
int getActiveRuntimeVirtualManifest(wrap::android::content::Context const &context, Json::Value &virtualManifest) {
293
jni::Array<std::string> projection = makeArray({active_runtime::Columns::PACKAGE_NAME, active_runtime::Columns::NATIVE_LIB_DIR,
294
active_runtime::Columns::SO_FILENAME, active_runtime::Columns::HAS_FUNCTIONS});
295
296
// First, try getting the installable broker's provider
297
bool systemBroker = false;
298
Cursor cursor;
299
if (!getActiveRuntimeCursor(context, projection, systemBroker, cursor)) {
300
// OK, try the system broker as a fallback.
301
systemBroker = true;
302
getActiveRuntimeCursor(context, projection, systemBroker, cursor);
303
}
304
305
if (cursor.isNull()) {
306
// Couldn't find either broker
307
ALOGE("Could access neither the installable nor system runtime broker.");
308
return -1;
309
}
310
311
cursor.moveToFirst();
312
313
do {
314
auto filename = cursor.getString(cursor.getColumnIndex(active_runtime::Columns::SO_FILENAME));
315
auto libDir = cursor.getString(cursor.getColumnIndex(active_runtime::Columns::NATIVE_LIB_DIR));
316
auto packageName = cursor.getString(cursor.getColumnIndex(active_runtime::Columns::PACKAGE_NAME));
317
318
auto hasFunctions = cursor.getInt(cursor.getColumnIndex(active_runtime::Columns::HAS_FUNCTIONS)) == 1;
319
ALOGI("Got runtime: package: %s, so filename: %s, native lib dir: %s, has functions: %s", packageName.c_str(),
320
filename.c_str(), libDir.c_str(), (hasFunctions ? "yes" : "no"));
321
322
auto lib_path = libDir + "/" + filename;
323
auto *lib = dlopen(lib_path.c_str(), RTLD_LAZY | RTLD_LOCAL);
324
if (lib) {
325
// we found a runtime that we can dlopen, use it.
326
dlclose(lib);
327
328
JsonManifestBuilder builder{"runtime", lib_path};
329
if (hasFunctions) {
330
int result = populateFunctions(context, systemBroker, packageName, builder);
331
if (result != 0) {
332
ALOGW("Unable to populate functions from runtime: %s, checking for more records...", lib_path.c_str());
333
continue;
334
}
335
}
336
virtualManifest = builder.build();
337
cursor.close();
338
return 0;
339
}
340
// this runtime was not accessible, see if the broker has more runtimes on
341
// offer.
342
ALOGV("Unable to open broker provided runtime at %s, checking for more records...", lib_path.c_str());
343
} while (cursor.moveToNext());
344
345
ALOGE("Unable to open any of the broker provided runtimes.");
346
cursor.close();
347
return -1;
348
}
349
} // namespace openxr_android
350
351
#endif // __ANDROID__
352
353