The open source OpenXR runtime
1// Copyright 2020, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Implementations for loading Java code from a package.
6 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
7 * @ingroup aux_android
8 */
9
10#include "android_load_class.hpp"
11
12#include "util/u_logging.h"
13
14#include "wrap/android.content.h"
15#include "wrap/android.content.pm.h"
16#include "wrap/dalvik.system.h"
17
18#include "jni.h"
19
20#include <dlfcn.h>
21
22using wrap::android::content::Context;
23using wrap::android::content::pm::ApplicationInfo;
24using wrap::android::content::pm::PackageManager;
25using wrap::dalvik::system::DexClassLoader;
26
27namespace xrt::auxiliary::android {
28
29static constexpr char kIntentAction[] = "org.khronos.openxr.OpenXRRuntimeService";
30
31/*!
32 * Hacky way to retrieve runtime source dir.
33 */
34static std::string
35getRuntimeSourceDir()
36{
37 Dl_info info{};
38 std::string dir;
39 if (dladdr((void *)&getRuntimeSourceDir, &info)) {
40 // dli_filename is full path of the library contains the symbol. For example:
41 // /data/app/~~sha27MVNR46wLF-96zA_LQ==/org.freedesktop.monado.openxr_runtime.out_of_process-cqs8L2Co3WfHGgvDwF12JA==/lib/arm64/libopenxr_monado.so
42 dir = info.dli_fname;
43
44 // Trim trailing lib path to .so (e.g. /lib/arm64/libopenxr_monado.so)
45 dir = dir.substr(0, dir.find("/lib/"));
46
47 // In case the SO is not extracted, trim off the base APK name and !
48 // This finishes handling cases like:
49 // /data/app/~~sha27MVNR46wLF-96zA_LQ==/org.freedesktop.monado.openxr_runtime.out_of_process-cqs8L2Co3WfHGgvDwF12JA==/base.apk!/lib/arm64/libopenxr_monado.so
50 dir = dir.substr(0, dir.find("/base.apk!"));
51 }
52
53 return dir;
54}
55
56ApplicationInfo
57getAppInfo(std::string const &packageName, jobject application_context)
58{
59 try {
60 auto context = Context{application_context};
61 if (context.isNull()) {
62 U_LOG_E("getAppInfo: application_context was null");
63 return {};
64 }
65 auto packageManager = PackageManager{context.getPackageManager()};
66 if (packageManager.isNull()) {
67 U_LOG_E(
68 "getAppInfo: "
69 "application_context.getPackageManager() returned null");
70 return {};
71 }
72 auto intent = wrap::android::content::Intent::construct(kIntentAction);
73 auto resolutions = packageManager.queryIntentServices(
74 intent, PackageManager::GET_META_DATA | PackageManager::GET_SHARED_LIBRARY_FILES);
75 if (resolutions.isNull() || resolutions.size() == 0) {
76 U_LOG_E(
77 "getAppInfo: "
78 "application_context.getPackageManager().queryIntentServices() returned null or empty");
79 return {};
80 }
81 const auto n = resolutions.size();
82 ApplicationInfo appInfo;
83 for (int32_t i = 0; i < n; ++i) {
84 wrap::android::content::pm::ResolveInfo resolution{resolutions.get(i)};
85 auto service = resolution.getServiceInfo();
86 if (service.isNull()) {
87 continue;
88 }
89 U_LOG_I("getAppInfo: Considering package %s", service.getPackageName().c_str());
90 if (service.getPackageName() == packageName) {
91 appInfo = service.getApplicationInfo();
92 break;
93 }
94 }
95
96 if (appInfo.isNull()) {
97 U_LOG_E(
98 "getAppInfo: "
99 "could not find a package advertising the intent %s named %s",
100 kIntentAction, packageName.c_str());
101 return {};
102 }
103 return appInfo;
104 } catch (std::exception const &e) {
105 U_LOG_E("Could not get App Info: %s", e.what());
106 return {};
107 }
108}
109
110wrap::java::lang::Class
111loadClassFromPackage(ApplicationInfo applicationInfo, jobject application_context, const char *clazz_name)
112{
113 auto context = Context{application_context}.getApplicationContext();
114 auto pkgContext = context.createPackageContext(
115 applicationInfo.getPackageName(), Context::CONTEXT_IGNORE_SECURITY | Context::CONTEXT_INCLUDE_CODE);
116
117 // Not using ClassLoader.loadClass because it expects a /-delimited
118 // class name, while we have a .-delimited class name.
119 // This does work
120 wrap::java::lang::ClassLoader pkgClassLoader = pkgContext.getClassLoader();
121
122 try {
123 auto loadedClass = pkgClassLoader.loadClass(std::string(clazz_name));
124 if (loadedClass.isNull()) {
125 U_LOG_E("Could not load class for name %s", clazz_name);
126 return wrap::java::lang::Class();
127 }
128
129 return loadedClass;
130 } catch (std::exception const &e) {
131 U_LOG_E("Could not load class '%s' forName: %s", clazz_name, e.what());
132 return wrap::java::lang::Class();
133 }
134}
135
136wrap::java::lang::Class
137loadClassFromApk(jobject application_context, const char *apk_path, const char *clazz_name)
138{
139 Context context = Context{application_context}.getApplicationContext();
140 DexClassLoader classLoader = DexClassLoader::construct(apk_path, "", context.getClassLoader().object());
141 try {
142 auto loadedClass = classLoader.loadClass(std::string(clazz_name));
143 if (loadedClass.isNull()) {
144 U_LOG_E("Could not load class for name %s from %s", clazz_name, apk_path);
145 return wrap::java::lang::Class();
146 }
147
148 return loadedClass;
149 } catch (std::exception const &e) {
150 U_LOG_E("Could not load class '%s' from '%s' forName: %s", clazz_name, apk_path, e.what());
151 return wrap::java::lang::Class();
152 }
153}
154
155wrap::java::lang::Class
156loadClassFromRuntimeApk(jobject application_context, const char *clazz_name)
157{
158 if (!application_context) {
159 U_LOG_E("Could not load class %s, invalid context", clazz_name);
160 return {};
161 }
162
163 std::string runtimeApkPath = getRuntimeSourceDir() + "/base.apk";
164 return loadClassFromApk(application_context, runtimeApkPath.c_str(), clazz_name);
165}
166
167} // namespace xrt::auxiliary::android
168
169
170void *
171android_load_class_from_package(struct _JavaVM *vm,
172 const char *pkgname,
173 void *application_context,
174 const char *classname)
175{
176 using namespace xrt::auxiliary::android;
177 jni::init(vm);
178 Context context((jobject)application_context);
179 auto info = getAppInfo(pkgname, (jobject)application_context);
180 if (info.isNull()) {
181 U_LOG_E("Could not get application info for package '%s'", pkgname);
182 return nullptr;
183 }
184 auto clazz = loadClassFromPackage(info, (jobject)application_context, classname);
185 if (clazz.isNull()) {
186 U_LOG_E("Could not load class '%s' from package '%s'", classname, pkgname);
187 return nullptr;
188 }
189 return clazz.object().getHandle();
190}