use super::sys::*;
use crate::CoreError;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::os::raw::{c_char, c_void};
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
const WINDOW_SERVICE: &[u8] = b"window\0";
const WM_DESCRIPTOR: &[u8] = b"android.view.IWindowManager\0";
const FPS_DESCRIPTOR: &[u8] = b"android.window.ITaskFpsCallback\0";
static FPS_VALUE: AtomicU32 = AtomicU32::new(0);
static FPS_SEEN: AtomicBool = AtomicBool::new(false);
static FPS_CODE: AtomicU32 = AtomicU32::new(0);
static FPS_READ_I32: AtomicUsize = AtomicUsize::new(0);
unsafe extern "C" fn wm_on_create(_: *mut c_void) -> *mut c_void {
std::ptr::null_mut()
}
unsafe extern "C" fn wm_on_destroy(_: *mut c_void) {}
unsafe extern "C" fn wm_on_transact(
_: *mut AIBinder,
_: u32,
_: *const AParcel,
_: *mut AParcel,
) -> BinderStatus {
STATUS_OK
}
unsafe extern "C" fn fps_on_create(userdata: *mut c_void) -> *mut c_void {
userdata
}
unsafe extern "C" fn fps_on_destroy(userdata: *mut c_void) {
if !userdata.is_null() {
unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
}
}
unsafe extern "C" fn fps_on_transact(
binder: *mut AIBinder,
code: u32,
in_parcel: *const AParcel,
_: *mut AParcel,
) -> BinderStatus {
if code != FPS_CODE.load(Ordering::Relaxed) {
return STATUS_UNKNOWN_TRANSACTION;
}
let read_addr = FPS_READ_I32.load(Ordering::Relaxed);
if read_addr != 0 {
let read_fn: unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus =
unsafe { std::mem::transmute(read_addr) };
let mut bits: i32 = 0;
if unsafe { read_fn(in_parcel, &mut bits) } == STATUS_OK {
FPS_VALUE.store(sanitize_fps(bits as u32), Ordering::Relaxed);
FPS_SEEN.store(true, Ordering::Release);
let get_user_data = GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner());
if let Some(get_user_data) = *get_user_data {
let userdata = unsafe { get_user_data(binder) };
if !userdata.is_null() {
let efd = userdata as *mut OwnedFd;
let val: u64 = 1;
unsafe {
libc::write((*efd).as_raw_fd(), &val as *const u64 as *const c_void, 8)
};
}
}
}
}
STATUS_OK
}
pub struct FpsListener {
_lib: DlHandle,
vt: Vtable,
window: OwnedBinder,
cb_binder: *mut AIBinder,
_wm_class: *mut AIBinder_Class,
register_code: u32,
unregister_code: u32,
task_id: i32,
}
unsafe impl Send for FpsListener {}
impl FpsListener {
pub fn open() -> Result<(Self, OwnedFd), 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 (register_code, unregister_code, on_fps_code) =
crate::android::dex::resolve_fps_codes().ok_or_else(|| {
CoreError::binder(-1, "dex:TRANSACTION_registerTaskFpsCallback not found")
})?;
let window = {
let raw = unsafe { (vt.get_service)(WINDOW_SERVICE.as_ptr() as *const c_char) };
if raw.is_null() {
return Err(CoreError::binder(-1, "AServiceManager_getService:window"));
}
OwnedBinder {
ptr: raw,
dec_strong: vt.dec_strong,
}
};
let wm_class = unsafe {
(vt.class_define)(
WM_DESCRIPTOR.as_ptr() as *const c_char,
wm_on_create,
wm_on_destroy,
wm_on_transact,
)
};
if wm_class.is_null() {
return Err(CoreError::binder(
-1,
"AIBinder_Class_define:IWindowManager",
));
}
unsafe { (vt.associate_class)(window.ptr, wm_class) };
let cb_class = unsafe {
(vt.class_define)(
FPS_DESCRIPTOR.as_ptr() as *const c_char,
fps_on_create,
fps_on_destroy,
fps_on_transact,
)
};
if cb_class.is_null() {
return Err(CoreError::binder(
-1,
"AIBinder_Class_define:ITaskFpsCallback",
));
}
let owned = unsafe {
let raw = libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC);
if raw < 0 {
return Err(CoreError::sys(*libc::__errno(), "eventfd"));
}
OwnedFd::from_raw_fd(raw)
};
let consumer = owned
.try_clone()
.map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:fps"))?;
let userdata = Box::into_raw(Box::new(owned)) as *mut c_void;
let cb_binder = unsafe { (vt.new_binder)(cb_class, userdata) };
if cb_binder.is_null() {
unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
return Err(CoreError::binder(-1, "AIBinder_new:ITaskFpsCallback"));
}
unsafe { (vt.associate_class)(cb_binder, cb_class) };
FPS_READ_I32.store(vt.read_int32 as usize, Ordering::Relaxed);
FPS_SEEN.store(false, Ordering::Relaxed);
FPS_CODE.store(on_fps_code, Ordering::Relaxed);
FPS_VALUE.store(0, Ordering::Relaxed);
*GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner()) = Some(vt.get_user_data);
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,
window,
cb_binder,
_wm_class: wm_class,
register_code,
unregister_code,
task_id: -1,
},
consumer,
))
}
pub fn register(&mut self, task_id: i32) -> Result<(), CoreError> {
if self.task_id == task_id {
return Ok(());
}
if self.task_id >= 0 {
let _ = self.unregister();
}
let _ = transact_write(&self.vt, self.window.ptr, self.register_code, |w| {
w.write_i32(task_id)?;
w.write_strong_binder(self.cb_binder)
})?;
self.task_id = task_id;
Ok(())
}
pub fn unregister(&mut self) -> Result<(), CoreError> {
if self.task_id < 0 {
return Ok(());
}
let _ = transact_write(&self.vt, self.window.ptr, self.unregister_code, |w| {
w.write_strong_binder(self.cb_binder)
})?;
self.task_id = -1;
Ok(())
}
pub fn last_fps(&self) -> Option<f32> {
fps_from_state(
FPS_SEEN.load(Ordering::Acquire),
FPS_VALUE.load(Ordering::Relaxed),
)
}
pub fn task_id(&self) -> Option<i32> {
(self.task_id >= 0).then_some(self.task_id)
}
}
impl Drop for FpsListener {
fn drop(&mut self) {
let _ = self.unregister();
}
}
fn fps_from_state(seen: bool, bits: u32) -> Option<f32> {
if seen {
Some(f32::from_bits(bits))
} else {
None
}
}
fn sanitize_fps(bits: u32) -> u32 {
let v = f32::from_bits(bits);
if v.is_finite() && v >= 0.0 {
bits
} else {
0.0f32.to_bits()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fps_zero_report_distinct_from_unseen() {
assert_eq!(fps_from_state(false, 0), None);
assert_eq!(fps_from_state(true, 0), Some(0.0));
assert_eq!(fps_from_state(true, 60.0f32.to_bits()), Some(60.0));
assert_eq!(fps_from_state(false, 60.0f32.to_bits()), None);
}
#[test]
fn sanitize_fps_rejects_non_finite_and_negative() {
let de = |bits| f32::from_bits(sanitize_fps(bits));
assert_eq!(de(0.0f32.to_bits()), 0.0);
assert_eq!(de(60.0f32.to_bits()), 60.0);
assert_eq!(de(f32::NAN.to_bits()), 0.0);
assert_eq!(de(f32::INFINITY.to_bits()), 0.0);
assert_eq!(de(f32::NEG_INFINITY.to_bits()), 0.0);
assert_eq!(de((-5.0f32).to_bits()), 0.0);
}
}