use super::sys::*;
use crate::CoreError;
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd};
use std::os::raw::{c_char, c_void};
use std::sync::Mutex;
struct ParsedDisplayInfo {
active_mode_id: i32,
vsync_rate: f32,
peak_refresh_rate: f32,
render_frame_rate: f32,
}
fn parse_display_info(r: &ParcelReader<'_>) -> Result<ParsedDisplayInfo, CoreError> {
r.skip_i32s(5)?;
if let Some(addr_class) = r.read_string()? {
if addr_class.ends_with("$Physical") {
r.read_int64()?;
} else if addr_class.ends_with("$Network") {
r.skip_string16()?;
} else {
return Err(CoreError::binder(
-1,
"display_info:unsupported DisplayAddress",
));
}
}
if let Some(_dpi_class) = r.read_string()? {
r.skip_string16()?; r.skip_string16()?; r.skip_value()?; r.skip_value()?; r.skip_value()?; r.read_i32()?; }
r.skip_string8()?;
r.skip_i32s(8)?;
let cutout_marker = r.read_i32()?;
if cutout_marker == 1 {
r.skip_typed_rect()?; let bounds = r.read_i32()?; if bounds >= 0 {
for _ in 0..bounds {
r.skip_typed_rect()?;
}
}
r.skip_typed_rect()?; r.skip_i32s(4)?; r.read_float()?; r.skip_string16()?; r.read_i32()?; r.read_float()?; r.read_float()?; }
r.read_i32()?; let active_mode_id = r.read_i32()?;
let render_frame_rate = {
let v = r.read_float()?;
if v.is_finite() && v >= 0.0 { v } else { 0.0 }
};
r.read_i32()?; let n_modes = r.read_i32()?.max(0) as usize;
let mut vsync_rate: f32 = 0.0;
let mut peak_refresh_rate: f32 = 0.0;
for _ in 0..n_modes {
let mode_id = r.read_i32()?;
r.read_i32()?; r.read_i32()?; let refresh_rate = {
let v = r.read_float()?;
if v.is_finite() && v >= 0.0 { v } else { 0.0 }
};
let alt = r.read_i32()?.max(0) as usize;
for _ in 0..alt {
let _f = r.read_float()?;
}
let hdr = r.read_i32()?.max(0) as usize;
for _ in 0..hdr {
r.read_i32()?;
}
peak_refresh_rate = peak_refresh_rate.max(refresh_rate);
if mode_id == active_mode_id {
vsync_rate = refresh_rate.max(vsync_rate);
}
}
Ok(ParsedDisplayInfo {
active_mode_id,
vsync_rate,
peak_refresh_rate,
render_frame_rate,
})
}
const DISPLAY_SERVICE: &[u8] = b"display\0";
const DISPLAY_DESCRIPTOR: &[u8] = b"android.hardware.display.IDisplayManager\0";
const CALLBACK_DESCRIPTOR: &[u8] = b"android.hardware.display.IDisplayManagerCallback\0";
const POWER_SERVICE: &[u8] = b"power\0";
const POWER_DESCRIPTOR: &[u8] = b"android.os.IPowerManager\0";
const TX_DISPLAY_REGISTER_CALLBACK: u32 = 4;
static DISP_EVENTFD: Mutex<Option<OwnedFd>> = Mutex::new(None);
fn disp_eventfd_guard() -> std::sync::MutexGuard<'static, Option<OwnedFd>> {
DISP_EVENTFD.lock().unwrap_or_else(|p| p.into_inner())
}
unsafe extern "C" fn disp_cb_on_create(_: *mut c_void) -> *mut c_void {
std::ptr::null_mut()
}
unsafe extern "C" fn disp_cb_on_destroy(_: *mut c_void) {}
unsafe extern "C" fn disp_cb_on_transact(
_: *mut AIBinder,
code: u32,
_: *const AParcel,
_: *mut AParcel,
) -> BinderStatus {
if code == 1 {
if let Some(fd) = disp_eventfd_guard().as_ref() {
let val: u64 = 1;
unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
}
}
STATUS_OK
}
unsafe extern "C" fn disp_client_on_create(_: *mut c_void) -> *mut c_void {
std::ptr::null_mut()
}
unsafe extern "C" fn disp_client_on_destroy(_: *mut c_void) {}
unsafe extern "C" fn disp_client_on_transact(
_: *mut AIBinder,
_: u32,
_: *const AParcel,
_: *mut AParcel,
) -> BinderStatus {
STATUS_UNKNOWN_TRANSACTION
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct DisplayInfo {
pub active_mode_id: i32,
pub vsync_rate: f32,
pub peak_refresh_rate: f32,
pub render_frame_rate: f32,
pub is_interactive: bool,
}
pub struct DisplayManager {
_lib: DlHandle,
vt: Vtable,
display: Option<OwnedBinder>,
display_info_tx: Option<u32>,
power: Option<OwnedBinder>,
is_interactive_tx: u32,
}
unsafe impl Send for DisplayManager {}
impl DisplayManager {
pub fn open_with_callback() -> Result<(Self, crate::fd::Fd), CoreError> {
let handle = unsafe {
libc::dlopen(
LIBBINDER_PATH.as_ptr() as *const c_char,
libc::RTLD_NOW | libc::RTLD_LOCAL,
)
};
if handle.is_null() {
return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
}
let lib = DlHandle;
let vt = load_vtable(handle)?;
let owned = unsafe {
let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
if raw < 0 {
return Err(CoreError::sys(*libc::__errno(), "eventfd"));
}
OwnedFd::from_raw_fd(raw)
};
let raw_display = unsafe { (vt.get_service)(DISPLAY_SERVICE.as_ptr() as *const c_char) };
if raw_display.is_null() {
return Err(CoreError::binder(-1, "AServiceManager_getService:display"));
}
let display_class = unsafe {
(vt.class_define)(
DISPLAY_DESCRIPTOR.as_ptr() as *const c_char,
disp_client_on_create,
disp_client_on_destroy,
disp_client_on_transact,
)
};
if display_class.is_null() {
return Err(CoreError::binder(
-1,
"AIBinder_Class_define:IDisplayManager",
));
}
unsafe { (vt.associate_class)(raw_display, display_class) };
let display = OwnedBinder {
ptr: raw_display,
dec_strong: vt.dec_strong,
};
let cb_class = unsafe {
(vt.class_define)(
CALLBACK_DESCRIPTOR.as_ptr() as *const c_char,
disp_cb_on_create,
disp_cb_on_destroy,
disp_cb_on_transact,
)
};
if cb_class.is_null() {
return Err(CoreError::binder(
-1,
"AIBinder_Class_define:DisplayCallback",
));
}
let cb_binder = unsafe { (vt.new_binder)(cb_class, std::ptr::null_mut()) };
if cb_binder.is_null() {
return Err(CoreError::binder(-1, "AIBinder_new:DisplayCallback"));
}
let _ = transact_write(&vt, display.ptr, TX_DISPLAY_REGISTER_CALLBACK, |w| {
w.write_strong_binder(cb_binder)
})?;
let power_class = unsafe {
(vt.class_define)(
POWER_DESCRIPTOR.as_ptr() as *const c_char,
disp_client_on_create,
disp_client_on_destroy,
disp_client_on_transact,
)
};
let power = if power_class.is_null() {
None
} else {
let raw = unsafe { (vt.get_service)(POWER_SERVICE.as_ptr() as *const c_char) };
if raw.is_null() {
None
} else {
unsafe { (vt.associate_class)(raw, power_class) };
Some(OwnedBinder {
ptr: raw,
dec_strong: vt.dec_strong,
})
}
};
let is_interactive_tx = crate::android::dex::resolve_is_interactive_tx()
.ok_or_else(|| CoreError::binder(-1, "dex:TRANSACTION_isInteractive not found"))?;
let display_info_tx = crate::android::dex::resolve_display_info_tx();
let efd_owned = owned
.try_clone()
.map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:display"))
.and_then(|dup| unsafe {
crate::fd::Fd::from_owned_raw_fd(dup.into_raw_fd(), "display.efd")
.map_err(|_| CoreError::binder(-1, "Fd::from_owned_raw_fd:display.efd"))
})?;
*disp_eventfd_guard() = Some(owned);
unsafe { (vt.set_thread_pool_max)(0) };
let join_fn = vt.join_thread_pool;
std::thread::spawn(move || unsafe { join_fn() });
Ok((
Self {
_lib: lib,
vt,
display: Some(display),
display_info_tx,
power,
is_interactive_tx,
},
efd_owned,
))
}
pub fn is_interactive(&self) -> Result<bool, CoreError> {
let power = self
.power
.as_ref()
.ok_or_else(|| CoreError::binder(-1, "power:unavailable"))?;
let out = transact_write(&self.vt, power.ptr, self.is_interactive_tx, |_| Ok(()))?;
let r = ParcelReader {
vt: &self.vt,
parcel: &out,
};
let ex = r.read_i32()?;
if ex != EX_NONE {
return Err(CoreError::binder(ex, "isInteractive:exception"));
}
r.read_bool()
}
pub fn info(&self, display_id: i32) -> Result<DisplayInfo, CoreError> {
let display = self
.display
.as_ref()
.ok_or_else(|| CoreError::binder(-1, "display:unavailable"))?;
let tx = self
.display_info_tx
.ok_or_else(|| CoreError::binder(-1, "display_info:tx unavailable"))?;
let out = transact_write(&self.vt, display.ptr, tx, |w| w.write_i32(display_id))?;
let r = ParcelReader {
vt: &self.vt,
parcel: &out,
};
let ex = r.read_i32()?;
if ex != EX_NONE {
return Err(CoreError::binder(ex, "getDisplayInfo:exception"));
}
let parsed = parse_display_info(&r)?;
let interactive = self.is_interactive().unwrap_or(false);
Ok(DisplayInfo {
active_mode_id: parsed.active_mode_id,
vsync_rate: parsed.vsync_rate,
peak_refresh_rate: parsed.peak_refresh_rate,
render_frame_rate: parsed.render_frame_rate,
is_interactive: interactive,
})
}
}