The open source OpenXR runtime
1// Copyright 2020, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Implementation of native code for Android custom surface.
6 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
7 * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
8 * @ingroup aux_android
9 */
10
11#include "android_custom_surface.h"
12#include "android_globals.h"
13#include "android_load_class.hpp"
14
15#include "xrt/xrt_config_android.h"
16#include "util/u_logging.h"
17
18#include "wrap/android.content.h"
19#include "wrap/android.hardware.display.h"
20#include "wrap/android.provider.h"
21#include "wrap/android.view.h"
22#include "wrap/android.graphics.h"
23#include "org.freedesktop.monado.auxiliary.hpp"
24
25#include <android/native_window_jni.h>
26
27using wrap::android::content::Context;
28using wrap::android::graphics::PixelFormat;
29using wrap::android::hardware::display::DisplayManager;
30using wrap::android::provider::Settings;
31using wrap::android::view::Display;
32using wrap::android::view::SurfaceHolder;
33using wrap::android::view::WindowManager_LayoutParams;
34using wrap::org::freedesktop::monado::auxiliary::MonadoView;
35using xrt::auxiliary::android::loadClassFromRuntimeApk;
36
37struct android_custom_surface
38{
39 explicit android_custom_surface();
40 ~android_custom_surface();
41
42 MonadoView monadoView{};
43 jni::Class monadoViewClass{};
44};
45
46
47android_custom_surface::android_custom_surface() {}
48
49android_custom_surface::~android_custom_surface()
50{
51 // Tell Java that native code is done with this.
52 try {
53 MonadoView::removeFromWindow(monadoView);
54 if (!monadoView.isNull()) {
55 monadoView.markAsDiscardedByNative();
56 }
57 } catch (std::exception const &e) {
58 // Must catch and ignore any exceptions in the destructor!
59 U_LOG_E("Failure while marking MonadoView as discarded: %s", e.what());
60 }
61}
62
63struct android_custom_surface *
64android_custom_surface_async_start(
65 struct _JavaVM *vm, void *context, int32_t display_id, const char *surface_title, int32_t preferred_display_mode_id)
66{
67 jni::init(vm);
68 try {
69 auto clazz = loadClassFromRuntimeApk((jobject)context, MonadoView::getFullyQualifiedTypeName());
70 if (clazz.isNull()) {
71 U_LOG_E("Could not load class '%s' from package '%s'", MonadoView::getFullyQualifiedTypeName(),
72 XRT_ANDROID_PACKAGE);
73 return nullptr;
74 }
75
76 // Teach the wrapper our class before we start to use it.
77 MonadoView::staticInitClass((jclass)clazz.object().getHandle());
78 std::unique_ptr<android_custom_surface> ret = std::make_unique<android_custom_surface>();
79
80 // the 0 is to avoid this being considered "temporary" and to
81 // create a global ref.
82 ret->monadoViewClass = jni::Class((jclass)clazz.object().getHandle(), 0);
83
84 if (ret->monadoViewClass.isNull()) {
85 U_LOG_E("monadoViewClass was null");
86 return nullptr;
87 }
88
89 std::string clazz_name = ret->monadoViewClass.getName();
90 if (clazz_name != MonadoView::getFullyQualifiedTypeName()) {
91 U_LOG_E("Unexpected class name: %s", clazz_name.c_str());
92 return nullptr;
93 }
94
95 Context ctx = Context((jobject)context);
96 Context displayContext;
97 int32_t type = 0;
98 // Not focusable
99 int32_t flags =
100 WindowManager_LayoutParams::FLAG_FULLSCREEN() | WindowManager_LayoutParams::FLAG_NOT_FOCUSABLE();
101
102 if (android_globals_is_instance_of_activity(android_globals_get_vm(), context)) {
103 displayContext = ctx;
104 type = WindowManager_LayoutParams::TYPE_APPLICATION();
105 } else {
106 // Out of process mode, determine which display should be used.
107 DisplayManager dm = DisplayManager(ctx.getSystemService(Context::DISPLAY_SERVICE()));
108 Display display = dm.getDisplay(display_id);
109 displayContext = ctx.createDisplayContext(display);
110 type = WindowManager_LayoutParams::TYPE_APPLICATION_OVERLAY();
111 }
112
113 int32_t width = 0;
114 int32_t height = 0;
115 if (preferred_display_mode_id < 0) {
116 preferred_display_mode_id = 0;
117 } else if (preferred_display_mode_id > 0) {
118 // Preferred display mode ID of 0 is used to indicate no preference in the layout params.
119 // Display mode id is either 0-based or 1-based depending on the API
120 width = MonadoView::getDisplayModeIdWidth(displayContext, display_id,
121 preferred_display_mode_id - 1);
122 height = MonadoView::getDisplayModeIdHeight(displayContext, display_id,
123 preferred_display_mode_id - 1);
124 if ((width == 0) || (height == 0)) {
125 U_LOG_W("Invalid preferred display mode id %d. Use default", preferred_display_mode_id);
126 preferred_display_mode_id = 0;
127 } else {
128 U_LOG_D("Setting mode id %d, width=%d, height=%d", preferred_display_mode_id, width,
129 height);
130 }
131 }
132
133 auto lp = [&] {
134 if (preferred_display_mode_id > 0) {
135 // When specifying a preferred mode id, need to explicitly set the width/height as well
136 return WindowManager_LayoutParams::construct(width, height, type, flags,
137 PixelFormat::OPAQUE());
138 } else {
139 return WindowManager_LayoutParams::construct(type, flags);
140 }
141 }();
142 lp.setTitle(surface_title);
143 ret->monadoView = MonadoView::attachToWindow(displayContext, ret.get(), lp);
144 lp.object().set("preferredDisplayModeId", preferred_display_mode_id);
145
146 return ret.release();
147 } catch (std::exception const &e) {
148 U_LOG_E(
149 "Could not start attaching our custom surface to activity: "
150 "%s",
151 e.what());
152 return nullptr;
153 }
154}
155
156void
157android_custom_surface_destroy(struct android_custom_surface **ptr_custom_surface)
158{
159 if (ptr_custom_surface == NULL) {
160 return;
161 }
162 struct android_custom_surface *custom_surface = *ptr_custom_surface;
163 if (custom_surface == NULL) {
164 return;
165 }
166 delete custom_surface;
167 *ptr_custom_surface = NULL;
168}
169
170ANativeWindow *
171android_custom_surface_wait_get_surface(struct android_custom_surface *custom_surface, uint64_t timeout_ms)
172{
173 SurfaceHolder surfaceHolder{};
174 try {
175 surfaceHolder = custom_surface->monadoView.waitGetSurfaceHolder(timeout_ms);
176
177 } catch (std::exception const &e) {
178 // do nothing right now.
179 U_LOG_E(
180 "Could not wait for our custom surface: "
181 "%s",
182 e.what());
183 return nullptr;
184 }
185
186 if (surfaceHolder.isNull()) {
187 return nullptr;
188 }
189 auto surf = surfaceHolder.getSurface();
190 if (surf.isNull()) {
191 return nullptr;
192 }
193 return ANativeWindow_fromSurface(jni::env(), surf.object().makeLocalReference());
194}
195
196bool
197android_custom_surface_get_display_metrics(struct _JavaVM *vm,
198 void *context,
199 struct xrt_android_display_metrics *out_metrics)
200{
201 jni::init(vm);
202
203 try {
204 auto clazz = loadClassFromRuntimeApk((jobject)context, MonadoView::getFullyQualifiedTypeName());
205 if (clazz.isNull()) {
206 U_LOG_E("Could not load class '%s' from package '%s'", MonadoView::getFullyQualifiedTypeName(),
207 XRT_ANDROID_PACKAGE);
208 return false;
209 }
210
211 // Teach the wrapper our class before we start to use it.
212 MonadoView::staticInitClass((jclass)clazz.object().getHandle());
213
214 jni::Object displayMetrics = MonadoView::getDisplayMetrics(Context((jobject)context));
215 //! @todo implement non-deprecated codepath for api 30+
216 float displayRefreshRate = MonadoView::getDisplayRefreshRate(Context((jobject)context));
217 if (displayRefreshRate == 0.0) {
218 U_LOG_W("Could not get refresh rate, returning 60hz");
219 displayRefreshRate = 60.0f;
220 }
221 std::vector<float> supported_refresh_rates =
222 MonadoView::getSupportedRefreshRates(Context((jobject)context));
223
224 struct xrt_android_display_metrics metrics = {
225 .width_pixels = displayMetrics.get<int>("widthPixels"),
226 .height_pixels = displayMetrics.get<int>("heightPixels"),
227 .density_dpi = displayMetrics.get<int>("densityDpi"),
228 .density = displayMetrics.get<float>("density"),
229 .scaled_density = displayMetrics.get<float>("scaledDensity"),
230 .xdpi = displayMetrics.get<float>("xdpi"),
231 .ydpi = displayMetrics.get<float>("ydpi"),
232 .refresh_rate = displayRefreshRate,
233 .refresh_rates = {},
234 .refresh_rate_count = (uint32_t)supported_refresh_rates.size(),
235 };
236 for (int i = 0; i < (int)metrics.refresh_rate_count; ++i) {
237 metrics.refresh_rates[i] = supported_refresh_rates[i];
238 }
239
240 *out_metrics = metrics;
241
242 return true;
243 } catch (std::exception const &e) {
244 U_LOG_E("Could not get display metrics: %s", e.what());
245 return false;
246 }
247}
248
249bool
250android_custom_surface_can_draw_overlays(struct _JavaVM *vm, void *context)
251{
252 jni::init(vm);
253 return Settings::canDrawOverlays(Context{(jobject)context});
254}
255
256float
257android_custom_surface_get_display_refresh_rate(struct _JavaVM *vm, void *context)
258{
259 jni::init(vm);
260 try {
261 auto clazz = loadClassFromRuntimeApk((jobject)context, MonadoView::getFullyQualifiedTypeName());
262 if (clazz.isNull()) {
263 U_LOG_E("Could not load class '%s' from package '%s'", MonadoView::getFullyQualifiedTypeName(),
264 XRT_ANDROID_PACKAGE);
265 return 0;
266 }
267
268 // Teach the wrapper our class before we start to use it.
269 MonadoView::staticInitClass((jclass)clazz.object().getHandle());
270
271 return MonadoView::getDisplayRefreshRate(Context((jobject)context));
272 } catch (std::exception const &e) {
273 U_LOG_E("Could not get display refresh rate: %s", e.what());
274 return 0;
275 }
276}