Path: blob/master/thirdparty/openxr/src/loader/android_utilities.cpp
9917 views
// Copyright (c) 2020-2025 The Khronos Group Inc.1// Copyright (c) 2020-2021, Collabora, Ltd.2//3// SPDX-License-Identifier: Apache-2.0 OR MIT4//5// Initial Author: Rylie Pavlik <[email protected]>67#include "android_utilities.h"89#ifdef __ANDROID__10#include <wrap/android.net.h>11#include <wrap/android.content.h>12#include <wrap/android.database.h>13#include <json/value.h>1415#include <openxr/openxr.h>1617#include <dlfcn.h>18#include <sstream>19#include <vector>20#include <android/log.h>2122#define LOG_TAG "OpenXR-Loader"23#include "android_logging.h"2425namespace openxr_android {26using wrap::android::content::ContentUris;27using wrap::android::content::Context;28using wrap::android::database::Cursor;29using wrap::android::net::Uri;30using wrap::android::net::Uri_Builder;3132// Code in here corresponds roughly to the Java "BrokerContract" class and subclasses.33namespace {34constexpr auto AUTHORITY = "org.khronos.openxr.runtime_broker";35constexpr auto SYSTEM_AUTHORITY = "org.khronos.openxr.system_runtime_broker";36constexpr auto BASE_PATH = "openxr";37constexpr auto ABI_PATH = "abi";38constexpr auto RUNTIMES_PATH = "runtimes";3940constexpr const char *getBrokerAuthority(bool systemBroker) { return systemBroker ? SYSTEM_AUTHORITY : AUTHORITY; }4142struct BaseColumns {43/**44* The unique ID for a row.45*/46[[maybe_unused]] static constexpr auto ID = "_id";47};4849/**50* Contains details for the /openxr/[major_ver]/abi/[abi]/runtimes/active URI.51* <p>52* This URI represents a "table" containing at most one item, the currently active runtime. The53* policy of which runtime is chosen to be active (if more than one is installed) is left to the54* content provider.55* <p>56* No sort order is required to be honored by the content provider.57*/58namespace active_runtime {59/**60* Final path component to this URI.61*/62static constexpr auto TABLE_PATH = "active";6364/**65* Create a content URI for querying the data on the active runtime for a66* given major version of OpenXR.67*68* @param systemBroker If the system runtime broker (instead of the installable one) should be queried.69* @param majorVer The major version of OpenXR.70* @param abi The Android ABI name in use.71* @return A content URI for a single item: the active runtime.72*/73static Uri makeContentUri(bool systemBroker, int majorVersion, const char *abi) {74auto builder = Uri_Builder::construct();75builder.scheme("content")76.authority(getBrokerAuthority(systemBroker))77.appendPath(BASE_PATH)78.appendPath(std::to_string(majorVersion))79.appendPath(ABI_PATH)80.appendPath(abi)81.appendPath(RUNTIMES_PATH)82.appendPath(TABLE_PATH);83return builder.build();84}8586struct Columns : BaseColumns {87/**88* Constant for the PACKAGE_NAME column name89*/90static constexpr auto PACKAGE_NAME = "package_name";9192/**93* Constant for the NATIVE_LIB_DIR column name94*/95static constexpr auto NATIVE_LIB_DIR = "native_lib_dir";9697/**98* Constant for the SO_FILENAME column name99*/100static constexpr auto SO_FILENAME = "so_filename";101102/**103* Constant for the HAS_FUNCTIONS column name.104* <p>105* If this column contains true, you should check the /functions/ URI for that runtime.106*/107static constexpr auto HAS_FUNCTIONS = "has_functions";108};109} // namespace active_runtime110111/**112* Contains details for the /openxr/[major_ver]/abi/[abi]/runtimes/[package]/functions URI.113* <p>114* This URI is for package-specific function name remapping. Since this is an optional field in115* the corresponding JSON manifests for OpenXR, it is optional here as well. If the active116* runtime contains "true" in its "has_functions" column, then this table must exist and be117* queryable.118* <p>119* No sort order is required to be honored by the content provider.120*/121namespace functions {122/**123* Final path component to this URI.124*/125static constexpr auto TABLE_PATH = "functions";126127/**128* Create a content URI for querying all rows of the function remapping data for a given129* runtime package and major version of OpenXR.130*131* @param systemBroker If the system runtime broker (instead of the installable one) should be queried.132* @param majorVer The major version of OpenXR.133* @param packageName The package name of the runtime.134* @param abi The Android ABI name in use.135* @return A content URI for the entire table: the function remapping for that runtime.136*/137static Uri makeContentUri(bool systemBroker, int majorVersion, std::string const &packageName, const char *abi) {138auto builder = Uri_Builder::construct();139builder.scheme("content")140.authority(getBrokerAuthority(systemBroker))141.appendPath(BASE_PATH)142.appendPath(std::to_string(majorVersion))143.appendPath(ABI_PATH)144.appendPath(abi)145.appendPath(RUNTIMES_PATH)146.appendPath(packageName)147.appendPath(TABLE_PATH);148return builder.build();149}150151struct Columns : BaseColumns {152/**153* Constant for the FUNCTION_NAME column name154*/155static constexpr auto FUNCTION_NAME = "function_name";156157/**158* Constant for the SYMBOL_NAME column name159*/160static constexpr auto SYMBOL_NAME = "symbol_name";161};162} // namespace functions163164} // namespace165166static inline jni::Array<std::string> makeArray(std::initializer_list<const char *> &&list) {167auto ret = jni::Array<std::string>{(long)list.size()};168long i = 0;169for (auto &&elt : list) {170ret.setElement(i, elt);171++i;172}173return ret;174}175176#if defined(__arm__)177static constexpr auto ABI = "armeabi-v7l";178#elif defined(__aarch64__)179static constexpr auto ABI = "arm64-v8a";180#elif defined(__i386__)181static constexpr auto ABI = "x86";182#elif defined(__x86_64__)183static constexpr auto ABI = "x86_64";184#else185#error "Unknown ABI!"186#endif187188/// Helper class to generate the jsoncpp object corresponding to a synthetic runtime manifest.189class JsonManifestBuilder {190public:191JsonManifestBuilder(const std::string &libraryPathParent, const std::string &libraryPath);192JsonManifestBuilder &function(const std::string &functionName, const std::string &symbolName);193194Json::Value build() const { return root_node; }195196private:197Json::Value root_node;198};199200inline JsonManifestBuilder::JsonManifestBuilder(const std::string &libraryPathParent, const std::string &libraryPath)201: root_node(Json::objectValue) {202root_node["file_format_version"] = "1.0.0";203root_node["instance_extensions"] = Json::Value(Json::arrayValue);204root_node["functions"] = Json::Value(Json::objectValue);205root_node[libraryPathParent] = Json::objectValue;206root_node[libraryPathParent]["library_path"] = libraryPath;207}208209inline JsonManifestBuilder &JsonManifestBuilder::function(const std::string &functionName, const std::string &symbolName) {210root_node["functions"][functionName] = symbolName;211return *this;212}213214static constexpr const char *getBrokerTypeName(bool systemBroker) { return systemBroker ? "system" : "installable"; }215216static int populateFunctions(wrap::android::content::Context const &context, bool systemBroker, const std::string &packageName,217JsonManifestBuilder &builder) {218jni::Array<std::string> projection = makeArray({functions::Columns::FUNCTION_NAME, functions::Columns::SYMBOL_NAME});219220auto uri = functions::makeContentUri(systemBroker, XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), packageName, ABI);221ALOGI("populateFunctions: Querying URI: %s", uri.toString().c_str());222223Cursor cursor = context.getContentResolver().query(uri, projection);224225if (cursor.isNull()) {226ALOGE("Null cursor when querying content resolver for functions.");227return -1;228}229if (cursor.getCount() < 1) {230ALOGE("Non-null but empty cursor when querying content resolver for functions.");231cursor.close();232return -1;233}234auto functionIndex = cursor.getColumnIndex(functions::Columns::FUNCTION_NAME);235auto symbolIndex = cursor.getColumnIndex(functions::Columns::SYMBOL_NAME);236while (cursor.moveToNext()) {237builder.function(cursor.getString(functionIndex), cursor.getString(symbolIndex));238}239240cursor.close();241return 0;242}243244// The current file relies on android-jni-wrappers and jnipp, which may throw on failure.245// This is problematic when the loader is compiled with exception handling disabled - the consumers can reasonably246// expect that the compilation with -fno-exceptions will succeed, but the compiler will not accept the code that247// uses `try` & `catch` keywords. We cannot use the `exception_handling.hpp` here since we're not at an ABI boundary,248// so we define helper macros here. This is fine for now since the only occurrence of exception-handling code is in this file.249#ifdef XRLOADER_DISABLE_EXCEPTION_HANDLING250251#define ANDROID_UTILITIES_TRY252#define ANDROID_UTILITIES_CATCH_FALLBACK(...)253254#else255256#define ANDROID_UTILITIES_TRY try257#define ANDROID_UTILITIES_CATCH_FALLBACK(...) \258catch (const std::exception &e) { \259__VA_ARGS__ \260}261262#endif // XRLOADER_DISABLE_EXCEPTION_HANDLING263264/// Get cursor for active runtime, parameterized by whether or not we use the system broker265static bool getActiveRuntimeCursor(wrap::android::content::Context const &context, jni::Array<std::string> const &projection,266bool systemBroker, Cursor &cursor) {267auto uri = active_runtime::makeContentUri(systemBroker, XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), ABI);268ALOGI("getActiveRuntimeCursor: Querying URI: %s", uri.toString().c_str());269270ANDROID_UTILITIES_TRY { cursor = context.getContentResolver().query(uri, projection); }271ANDROID_UTILITIES_CATCH_FALLBACK({272ALOGW("Exception when querying %s content resolver: %s", getBrokerTypeName(systemBroker), e.what());273cursor = {};274return false;275})276277if (cursor.isNull()) {278ALOGW("Null cursor when querying %s content resolver.", getBrokerTypeName(systemBroker));279cursor = {};280return false;281}282if (cursor.getCount() < 1) {283ALOGW("Non-null but empty cursor when querying %s content resolver.", getBrokerTypeName(systemBroker));284cursor.close();285cursor = {};286return false;287}288return true;289}290291int getActiveRuntimeVirtualManifest(wrap::android::content::Context const &context, Json::Value &virtualManifest) {292jni::Array<std::string> projection = makeArray({active_runtime::Columns::PACKAGE_NAME, active_runtime::Columns::NATIVE_LIB_DIR,293active_runtime::Columns::SO_FILENAME, active_runtime::Columns::HAS_FUNCTIONS});294295// First, try getting the installable broker's provider296bool systemBroker = false;297Cursor cursor;298if (!getActiveRuntimeCursor(context, projection, systemBroker, cursor)) {299// OK, try the system broker as a fallback.300systemBroker = true;301getActiveRuntimeCursor(context, projection, systemBroker, cursor);302}303304if (cursor.isNull()) {305// Couldn't find either broker306ALOGE("Could access neither the installable nor system runtime broker.");307return -1;308}309310cursor.moveToFirst();311312do {313auto filename = cursor.getString(cursor.getColumnIndex(active_runtime::Columns::SO_FILENAME));314auto libDir = cursor.getString(cursor.getColumnIndex(active_runtime::Columns::NATIVE_LIB_DIR));315auto packageName = cursor.getString(cursor.getColumnIndex(active_runtime::Columns::PACKAGE_NAME));316317auto hasFunctions = cursor.getInt(cursor.getColumnIndex(active_runtime::Columns::HAS_FUNCTIONS)) == 1;318ALOGI("Got runtime: package: %s, so filename: %s, native lib dir: %s, has functions: %s", packageName.c_str(),319filename.c_str(), libDir.c_str(), (hasFunctions ? "yes" : "no"));320321auto lib_path = libDir + "/" + filename;322auto *lib = dlopen(lib_path.c_str(), RTLD_LAZY | RTLD_LOCAL);323if (lib) {324// we found a runtime that we can dlopen, use it.325dlclose(lib);326327JsonManifestBuilder builder{"runtime", lib_path};328if (hasFunctions) {329int result = populateFunctions(context, systemBroker, packageName, builder);330if (result != 0) {331ALOGW("Unable to populate functions from runtime: %s, checking for more records...", lib_path.c_str());332continue;333}334}335virtualManifest = builder.build();336cursor.close();337return 0;338}339// this runtime was not accessible, see if the broker has more runtimes on340// offer.341ALOGV("Unable to open broker provided runtime at %s, checking for more records...", lib_path.c_str());342} while (cursor.moveToNext());343344ALOGE("Unable to open any of the broker provided runtimes.");345cursor.close();346return -1;347}348} // namespace openxr_android349350#endif // __ANDROID__351352353