The open source OpenXR runtime
at main 276 lines 9.2 kB view raw
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}