Skip to content

Commit

Permalink
Android: fully implement Monitor/VideoModeHandle
Browse files Browse the repository at this point in the history
  • Loading branch information
daxpedda committed Jul 25, 2024
1 parent 61091d7 commit 694fa95
Show file tree
Hide file tree
Showing 4 changed files with 305 additions and 30 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ softbuffer = { version = "0.4.0", default-features = false, features = [
# Android
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.6.0"
jni = "0.21"
ndk = { version = "0.9.0", default-features = false }
ndk-context = "0.1"

# AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies]
Expand Down
1 change: 1 addition & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ changelog entry.
information is available. This "detailed monitors" can be used in `Window::set_fullscreen()` as
well.
- Add `MonitorHandle::current_video_mode()`.
- On Android, fully implement `MonitorHandle` and `VideoModeHandle`.

### Changed

Expand Down
167 changes: 167 additions & 0 deletions src/platform_impl/android/ffi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use android_activity::AndroidApp;
use jni::objects::{JObject, JObjectArray, JString, JValueOwned};
use jni::{AttachGuard, JavaVM};

pub fn jvm(app: &AndroidApp) -> JavaVM {
unsafe { JavaVM::from_raw(app.vm_as_ptr() as _) }.expect("unexpected null pointer")
}

pub fn env(jvm: &JavaVM) -> AttachGuard<'_> {
jvm.attach_current_thread().expect("unexpected failure accessing `JNIEnv`")
}

pub fn activity<'e>(app: &AndroidApp, _: &AttachGuard<'e>) -> Activity<'e> {
Activity(unsafe { JObject::from_raw(app.activity_as_ptr() as _) })
}

pub struct Activity<'e>(JObject<'e>);

impl Activity<'_> {
pub fn window_manager<'e>(&self, env: &mut AttachGuard<'e>) -> WindowManager<'e> {
WindowManager(
env.call_method(&self.0, "getWindowManager", "()Landroid/view/WindowManager;", &[])
.and_then(JValueOwned::l)
.expect("`Activity.getWindowManager()` is not expected to fail"),
)
}
}

pub fn context<'e>(_: &mut AttachGuard<'e>) -> Context<'e> {
Context(unsafe { JObject::from_raw(ndk_context::android_context().context() as _) })
}

pub struct Context<'e>(JObject<'e>);

impl Context<'_> {
pub fn display_manager<'e>(&self, env: &mut AttachGuard<'e>) -> DisplayManager<'e> {
let class = env.get_object_class(&self.0).expect("failed to find `Context` class");
let display_service = env
.get_static_field(&class, "DISPLAY_SERVICE", "Ljava/lang/String;")
.expect("failed to find `Context.DISPLAY_SERVICE");
let display_manager = env
.call_method(&self.0, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", &[
(&display_service).into(),
])
.and_then(JValueOwned::l)
.expect(
"`Context.getSystemService()` is not expected to fail with \
`Context.DISPLAY_SERVICE`",
);

DisplayManager(display_manager)
}
}

pub struct DisplayManager<'e>(JObject<'e>);

impl DisplayManager<'_> {
pub fn display<'e>(&self, env: &mut AttachGuard<'e>, display_id: i32) -> Option<Display<'e>> {
env.call_method(&self.0, "getDisplay", "(I)Landroid/view/Display;", &[display_id.into()])
.and_then(JValueOwned::l)
.map(Display)
.ok()
}

pub fn displays<'e>(&self, env: &mut AttachGuard<'e>) -> Vec<Display<'e>> {
let displays = JObjectArray::from(
env.call_method(&self.0, "getDisplays", "()[Landroid/view/Display;", &[])
.and_then(JValueOwned::l)
.expect("`DisplayManager.getDisplays()` is not expected to fail"),
);

let length = env.get_array_length(&displays).unwrap();

(0..length)
.map(|index| env.get_object_array_element(&displays, index).map(Display).unwrap())
.collect()
}
}

pub struct WindowManager<'e>(JObject<'e>);

impl WindowManager<'_> {
pub fn default_display<'e>(&self, env: &mut AttachGuard<'e>) -> Display<'e> {
Display(
env.call_method(&self.0, "getDefaultDisplay", "()Landroid/view/Display;", &[])
.and_then(JValueOwned::l)
.expect("`WindowManager.getDefaultDisplay()` is not expected to fail"),
)
}
}

pub struct Display<'e>(JObject<'e>);

impl Display<'_> {
pub fn default_display(env: &mut AttachGuard<'_>) -> i32 {
let class = env.find_class("android/view/Display").expect("failed to find `Display`");
env.get_static_field(&class, "DEFAULT_DISPLAY", "I;")
.and_then(JValueOwned::i)
.expect("failed to find `Display.DEFAULT_DISPLAY")
}

pub fn display_id(&self, env: &mut AttachGuard<'_>) -> i32 {
env.call_method(&self.0, "getDisplayId", "()I", &[])
.and_then(JValueOwned::i)
.expect("`Display.getDisplayId()` is not expected to fail")
}

pub fn name(&self, env: &mut AttachGuard<'_>) -> String {
let name = JString::from(
env.call_method(&self.0, "getName", "()Ljava/lang/String;", &[])
.and_then(JValueOwned::l)
.expect("`Display.name()` is not expected to fail"),
);
let name = env.get_string(&name).unwrap();
name.to_string_lossy().to_string()
}

pub fn mode<'e>(&self, env: &mut AttachGuard<'e>) -> Mode<'e> {
Mode(
env.call_method(&self.0, "getMode", "()Landroid/view/Display$Mode;", &[])
.and_then(JValueOwned::l)
.expect("`Display.getMode()` is not expected to fail"),
)
}

pub fn supported_modes<'e>(&self, env: &mut AttachGuard<'e>) -> Vec<Mode<'e>> {
let modes = JObjectArray::from(
env.call_method(&self.0, "getSupportedModes", "()[Landroid/view/Display$Mode;", &[])
.and_then(JValueOwned::l)
.expect("`Display.getSupportedModes()` is not expected to fail"),
);

let length = env.get_array_length(&modes).unwrap();

(0..length)
.map(|index| env.get_object_array_element(&modes, index).map(Mode).unwrap())
.collect()
}
}

pub struct Mode<'e>(JObject<'e>);

impl Mode<'_> {
pub fn mode_id(&self, env: &mut AttachGuard<'_>) -> i32 {
env.call_method(&self.0, "getModeId", "()I", &[])
.and_then(JValueOwned::i)
.expect("`Display.Mode.getModeId()` is not expected to fail")
}

pub fn physical_width(&self, env: &mut AttachGuard<'_>) -> i32 {
env.call_method(&self.0, "getPhysicalWidth", "()I", &[])
.and_then(JValueOwned::i)
.expect("`Display.Mode.getPhysicalWidth()` is not expected to fail")
}

pub fn physical_height(&self, env: &mut AttachGuard<'_>) -> i32 {
env.call_method(&self.0, "getPhysicalHeight", "()I", &[])
.and_then(JValueOwned::i)
.expect("`Display.Mode.getPhysicalHeight()` is not expected to fail")
}

pub fn refresh_rate(&self, env: &mut AttachGuard<'_>) -> f32 {
env.call_method(&self.0, "getRefreshRate", "()F", &[])
.and_then(JValueOwned::f)
.expect("`Display.Mode.getRefreshRate()` is not expected to fail")
}
}
Loading

0 comments on commit 694fa95

Please sign in to comment.