Skip to main content

coreshift_core/binder/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/
4
5//! NDK binder primitives for querying Android system services.
6//!
7//! Uses `dlopen` on `libbinder_ndk.so` to avoid hard-linking against a library
8//! absent from older NDK toolchains or non-Android targets.
9//!
10//! ## Transaction code resolution
11//!
12//! Transaction codes are resolved fresh from `framework.jar` DEX on each
13//! startup; no persistent cache.
14//!
15//! ## Observer mode
16//!
17//! `ActivityManagerBinder::open_with_observer` registers this process as an
18//! `IProcessObserver` with ActivityManager. Callbacks fire when foreground
19//! activities change and signal an `eventfd` that callers can poll via epoll.
20//! After the eventfd fires, call `get_focused_package` to read the new value.
21
22// ─────────────────────────────────────────────────────────────────────────────
23// Android-only implementation
24// ─────────────────────────────────────────────────────────────────────────────
25
26#[cfg(target_os = "android")]
27mod imp {
28    use crate::CoreError;
29    use crate::dex;
30    use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd};
31    use std::os::raw::{c_char, c_void};
32    use std::sync::atomic::{AtomicI32, AtomicU32, AtomicUsize, Ordering};
33    use std::sync::Mutex;
34
35    // ── NDK binder status codes ───────────────────────────────────────────────
36
37    const STATUS_OK: i32 = 0;
38    const STATUS_UNKNOWN_TRANSACTION: i32 = -2;
39    const EX_NONE: i32 = 0;
40
41    // ── Interface constants ───────────────────────────────────────────────────
42
43    const AM_DESCRIPTOR:    &[u8] = b"android.app.IActivityManager\0";
44    const OBS_DESCRIPTOR:   &[u8] = b"android.app.IProcessObserver\0";
45    const FGPROC_DESCRIPTOR: &[u8] = b"android.app.IForegroundProcessObserver\0";
46    const ACTIVITY_SERVICE: &[u8] = b"activity\0";
47    #[cfg(target_pointer_width = "64")]
48    const LIBBINDER_PATH: &[u8] = b"/system/lib64/libbinder_ndk.so\0";
49    #[cfg(target_pointer_width = "32")]
50    const LIBBINDER_PATH: &[u8] = b"/system/lib/libbinder_ndk.so\0";
51
52    // ── Tx code cache ─────────────────────────────────────────────────────────
53    // Format (watcher.c compatible): observer_code query_code api_mode fg_code
54    // api_mode: 1 = getFocusedRootTaskInfo, 2 = getFocusedStackInfo (API 29)
55
56    // ── Statics for observer callback (binder thread pool context) ────────────
57    // The core owns the eventfd for the observer lifetime; the consumer receives
58    // a dup and may close it freely. The callback only ever writes to the core's
59    // copy, so it can never touch a closed/recycled fd (C2). The mutex guards
60    // publication/revocation against a callback firing concurrently.
61
62    static OBS_FG_CODE: AtomicU32 = AtomicU32::new(0);
63    static OBS_EVENTFD:  Mutex<Option<OwnedFd>> = Mutex::new(None);
64
65    fn obs_eventfd_guard() -> std::sync::MutexGuard<'static, Option<OwnedFd>> {
66        OBS_EVENTFD.lock().unwrap_or_else(|p| p.into_inner())
67    }
68
69    // ── IForegroundProcessObserver statics ───────────────────────────────────
70    // Separate from the IProcessObserver pair; same C2 discipline (core owns
71    // the eventfd, consumer gets a dup). FGPROC_READ_I32 holds the vtable's
72    // AParcel_readInt32 fn pointer so the callback can decode the `int pid`
73    // argument without owning the Vtable; it is published (non-zero) before
74    // FGPROC_FG_CODE/eventfd, so the callback never races an unset reader.
75
76    static FGPROC_FG_CODE: AtomicU32 = AtomicU32::new(0);
77    static FGPROC_PID: AtomicI32 = AtomicI32::new(0);
78    static FGPROC_EVENTFD: Mutex<Option<OwnedFd>> = Mutex::new(None);
79    static FGPROC_READ_I32: AtomicUsize = AtomicUsize::new(0);
80    // Binder class mode: 0 = stock IForegroundProcessObserver (single int pid),
81    // 1 = custom IProcessObserver (pid, uid, fg triplets). Only one of the two
82    // is ever configured by open_with_fgproc_observer; the callback branches on
83    // this to decode the in-parcel layout it actually receives.
84    static FGPROC_IPROC_MODE: AtomicU32 = AtomicU32::new(0);
85
86    fn fgproc_eventfd_guard() -> std::sync::MutexGuard<'static, Option<OwnedFd>> {
87        FGPROC_EVENTFD.lock().unwrap_or_else(|p| p.into_inner())
88    }
89
90    // ── Raw NDK type aliases ──────────────────────────────────────────────────
91
92    type AIBinder = c_void;
93    #[allow(non_camel_case_types)]
94    type AIBinder_Class = c_void;
95    type AParcel = c_void;
96    type BinderStatus = i32;
97    type StringAllocator = unsafe extern "C" fn(*mut c_void, i32, *mut *mut c_char) -> bool;
98
99    // ── AIBinder_Class callbacks ──────────────────────────────────────────────
100
101    // AM client — no-op server side (we're a client only)
102    unsafe extern "C" fn am_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
103    unsafe extern "C" fn am_on_destroy(_: *mut c_void) {}
104    unsafe extern "C" fn am_on_transact(
105        _: *mut AIBinder, _: u32, _: *const AParcel, _: *mut AParcel,
106    ) -> BinderStatus { STATUS_UNKNOWN_TRANSACTION }
107
108    // IProcessObserver server callbacks
109    unsafe extern "C" fn obs_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
110    unsafe extern "C" fn obs_on_destroy(_: *mut c_void) {}
111    unsafe extern "C" fn obs_on_transact(
112        _: *mut AIBinder, code: u32, _: *const AParcel, _: *mut AParcel,
113    ) -> BinderStatus {
114        if code == OBS_FG_CODE.load(Ordering::Relaxed) {
115            // Write while holding the lock: the fd can only be closed while
116            // we hold it, so a revoke can never race us into a stale number.
117            if let Some(fd) = obs_eventfd_guard().as_ref() {
118                let val: u64 = 1;
119                unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
120            }
121        }
122        STATUS_OK
123    }
124
125    // IForegroundProcessObserver server callbacks. Two parcel layouts, selected
126    // by FGPROC_IPROC_MODE:
127    //  - mode 0 (stock): `onForegroundProcessChanged(int pid)` — single int32.
128    //  - mode 1 (custom ROMs without IForegroundProcessObserver): the ROM
129    //    repurposes `IProcessObserver.onForegroundActivitiesChanged` to deliver
130    //    `(int pid, int uid, int fg)`. The callback stores the pid and only
131    //    signals the eventfd when fg != 0 (a foreground transition), so
132    //    background transitions never cause the daemon to react.
133    unsafe extern "C" fn fgproc_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
134    unsafe extern "C" fn fgproc_on_destroy(_: *mut c_void) {}
135    unsafe extern "C" fn fgproc_on_transact(
136        _: *mut AIBinder, code: u32, in_parcel: *const AParcel, _: *mut AParcel,
137    ) -> BinderStatus {
138        if code != FGPROC_FG_CODE.load(Ordering::Relaxed) {
139            return STATUS_UNKNOWN_TRANSACTION;
140        }
141        // Read fn is published non-zero before the code/eventfd, so a matching
142        // code is never paired with an unset reader.
143        let read_addr = FGPROC_READ_I32.load(Ordering::Relaxed);
144        if read_addr != 0 {
145            let read_fn: unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus =
146                unsafe { std::mem::transmute(read_addr) };
147            if FGPROC_IPROC_MODE.load(Ordering::Relaxed) == 1 {
148                // IProcessObserver.onForegroundActivitiesChanged(pid, uid, fg)
149                let mut pid: i32 = 0;
150                let mut _uid: i32 = 0;
151                let mut fg: i32 = 0;
152                let mut ok = unsafe { read_fn(in_parcel, &mut pid) } == STATUS_OK;
153                ok &= unsafe { read_fn(in_parcel, &mut _uid) } == STATUS_OK;
154                ok &= unsafe { read_fn(in_parcel, &mut fg) } == STATUS_OK;
155                if ok {
156                    FGPROC_PID.store(pid, Ordering::Relaxed);
157                    // Only foreground transitions are actionable; suppress the
158                    // background transition entirely (fg == 0).
159                    if fg == 0 {
160                        return STATUS_OK;
161                    }
162                } else {
163                    return STATUS_OK;
164                }
165            } else {
166                let mut pid: i32 = 0;
167                if unsafe { read_fn(in_parcel, &mut pid) } == STATUS_OK {
168                    FGPROC_PID.store(pid, Ordering::Relaxed);
169                }
170            }
171        }
172        // Write while holding the lock: the fd can only be closed while we
173        // hold it, so a revoke can never race us into a stale number.
174        if let Some(fd) = fgproc_eventfd_guard().as_ref() {
175            let val: u64 = 1;
176            unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
177        }
178        STATUS_OK
179    }
180
181    /// The PID captured by the most recent `onForegroundProcessChanged`
182    /// callback (requires `open_with_fgproc_observer`).
183    pub fn last_foreground_pid() -> i32 {
184        FGPROC_PID.load(Ordering::Relaxed)
185    }
186
187    // ── String allocator ─────────────────────────────────────────────────────
188
189    unsafe extern "C" fn string_alloc(
190        cookie: *mut c_void, length: i32, buffer: *mut *mut c_char,
191    ) -> bool {
192        if length < 0 { return true; }
193        let s = unsafe { &mut *(cookie as *mut StringBuf) };
194        s.0.reserve_exact(length as usize + 1);
195        unsafe { s.0.as_mut_vec().resize(length as usize + 1, 0) };
196        unsafe { *buffer = s.0.as_mut_ptr() as *mut c_char };
197        true
198    }
199
200    struct StringBuf(String);
201    impl StringBuf {
202        fn new() -> Self { Self(String::new()) }
203        fn finish(mut self) -> Option<String> {
204            if let Some(pos) = self.0.as_bytes().iter().position(|&b| b == 0) {
205                unsafe { self.0.as_mut_vec().truncate(pos) };
206            }
207            if self.0.is_empty() { None } else { Some(self.0) }
208        }
209    }
210
211    // ── Vtable ────────────────────────────────────────────────────────────────
212
213    struct Vtable {
214        get_service:         unsafe extern "C" fn(*const c_char) -> *mut AIBinder,
215        class_define:        unsafe extern "C" fn(
216                                 *const c_char,
217                                 unsafe extern "C" fn(*mut c_void) -> *mut c_void,
218                                 unsafe extern "C" fn(*mut c_void),
219                                 unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel) -> BinderStatus,
220                             ) -> *mut AIBinder_Class,
221        associate_class:     unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool,
222        new_binder:          unsafe extern "C" fn(*const AIBinder_Class, *mut c_void) -> *mut AIBinder,
223        prepare_transaction: unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus,
224        transact:            unsafe extern "C" fn(*mut AIBinder, u32, *mut *mut AParcel, *mut *mut AParcel, u32) -> BinderStatus,
225        dec_strong:          unsafe extern "C" fn(*mut AIBinder),
226        parcel_delete:       unsafe extern "C" fn(*mut AParcel),
227        read_int32:          unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus,
228        read_string:         unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus,
229        write_strong_binder: unsafe extern "C" fn(*mut AParcel, *mut AIBinder) -> BinderStatus,
230        set_thread_pool_max: unsafe extern "C" fn(u32),
231        join_thread_pool:    unsafe extern "C" fn(),
232        write_int32:         unsafe extern "C" fn(*mut AParcel, i32) -> BinderStatus,
233        // Optional: only present on API 29+, but all modern Android has this
234        read_bool:           Option<unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus>,
235    }
236
237    // ── RAII wrappers ─────────────────────────────────────────────────────────
238
239    struct DlHandle(*mut c_void);
240    unsafe impl Send for DlHandle {}
241    impl Drop for DlHandle {
242        fn drop(&mut self) {
243            // Intentionally no dlclose: the binder thread pool spawned in
244            // open_with_observer() keeps executing library code until process
245            // exit. Unloading the library while that thread runs causes
246            // use-after-free. libbinder_ndk.so is never unloaded during the
247            // daemon lifetime; the OS reclaims it on exit.
248        }
249    }
250
251    struct OwnedParcel { ptr: *mut AParcel, delete: unsafe extern "C" fn(*mut AParcel) }
252    impl Drop for OwnedParcel {
253        fn drop(&mut self) { if !self.ptr.is_null() { unsafe { (self.delete)(self.ptr) }; } }
254    }
255
256    struct OwnedBinder { ptr: *mut AIBinder, dec_strong: unsafe extern "C" fn(*mut AIBinder) }
257    unsafe impl Send for OwnedBinder {}
258    impl Drop for OwnedBinder {
259        fn drop(&mut self) { if !self.ptr.is_null() { unsafe { (self.dec_strong)(self.ptr) }; } }
260    }
261
262    // ── dlsym helper ─────────────────────────────────────────────────────────
263
264    macro_rules! dlsym_fn {
265        ($handle:expr, $name:literal, $ty:ty) => {{
266            let sym = unsafe {
267                libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char)
268            };
269            if sym.is_null() {
270                return Err(CoreError::binder(-1, concat!("dlsym:", $name)));
271            }
272            unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }
273        }};
274    }
275
276    macro_rules! dlsym_opt {
277        ($handle:expr, $name:literal, $ty:ty) => {{
278            let sym = unsafe {
279                libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char)
280            };
281            if sym.is_null() { None }
282            else { Some(unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }) }
283        }};
284    }
285
286    fn load_vtable(handle: *mut c_void) -> Result<Vtable, CoreError> {
287        Ok(Vtable {
288            get_service: dlsym_fn!(handle, "AServiceManager_getService",
289                unsafe extern "C" fn(*const c_char) -> *mut AIBinder),
290            class_define: dlsym_fn!(handle, "AIBinder_Class_define",
291                unsafe extern "C" fn(
292                    *const c_char,
293                    unsafe extern "C" fn(*mut c_void) -> *mut c_void,
294                    unsafe extern "C" fn(*mut c_void),
295                    unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel) -> BinderStatus,
296                ) -> *mut AIBinder_Class),
297            associate_class: dlsym_fn!(handle, "AIBinder_associateClass",
298                unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool),
299            new_binder: dlsym_fn!(handle, "AIBinder_new",
300                unsafe extern "C" fn(*const AIBinder_Class, *mut c_void) -> *mut AIBinder),
301            prepare_transaction: dlsym_fn!(handle, "AIBinder_prepareTransaction",
302                unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus),
303            transact: dlsym_fn!(handle, "AIBinder_transact",
304                unsafe extern "C" fn(*mut AIBinder, u32, *mut *mut AParcel, *mut *mut AParcel, u32) -> BinderStatus),
305            dec_strong: dlsym_fn!(handle, "AIBinder_decStrong",
306                unsafe extern "C" fn(*mut AIBinder)),
307            parcel_delete: dlsym_fn!(handle, "AParcel_delete",
308                unsafe extern "C" fn(*mut AParcel)),
309            read_int32: dlsym_fn!(handle, "AParcel_readInt32",
310                unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus),
311            read_string: dlsym_fn!(handle, "AParcel_readString",
312                unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus),
313            write_strong_binder: dlsym_fn!(handle, "AParcel_writeStrongBinder",
314                unsafe extern "C" fn(*mut AParcel, *mut AIBinder) -> BinderStatus),
315            set_thread_pool_max: dlsym_fn!(handle, "ABinderProcess_setThreadPoolMaxThreadCount",
316                unsafe extern "C" fn(u32)),
317            join_thread_pool: dlsym_fn!(handle, "ABinderProcess_joinThreadPool",
318                unsafe extern "C" fn()),
319            write_int32: dlsym_fn!(handle, "AParcel_writeInt32",
320                unsafe extern "C" fn(*mut AParcel, i32) -> BinderStatus),
321            read_bool: dlsym_opt!(handle, "AParcel_readBool",
322                unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus),
323        })
324    }
325
326    // ── ParcelReader ──────────────────────────────────────────────────────────
327
328    struct ParcelReader<'a> { vt: &'a Vtable, parcel: &'a OwnedParcel }
329
330    impl<'a> ParcelReader<'a> {
331        fn read_i32(&self) -> Result<i32, CoreError> {
332            let mut v = 0i32;
333            let s = unsafe { (self.vt.read_int32)(self.parcel.ptr, &mut v) };
334            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readInt32")); }
335            Ok(v)
336        }
337        fn read_string(&self) -> Result<Option<String>, CoreError> {
338            let mut buf = StringBuf::new();
339            let s = unsafe {
340                (self.vt.read_string)(self.parcel.ptr, &mut buf as *mut StringBuf as *mut c_void, string_alloc)
341            };
342            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readString")); }
343            Ok(buf.finish())
344        }
345        fn skip_i32s(&self, n: usize) -> Result<(), CoreError> {
346            for _ in 0..n { self.read_i32()?; }
347            Ok(())
348        }
349        fn skip_int_array(&self) -> Result<(), CoreError> {
350            let count = self.read_i32()?.max(0) as usize;
351            self.skip_i32s(count)
352        }
353        fn read_first_package_from_names(&self) -> Result<Option<String>, CoreError> {
354            let count = self.read_i32()?.max(0) as usize;
355            let mut first: Option<String> = None;
356            for _ in 0..count {
357                let s = self.read_string()?;
358                if first.is_none() {
359                    first = s.and_then(|c| c.split('/').next().map(str::to_owned));
360                }
361            }
362            Ok(first)
363        }
364    }
365
366    // ── Response parsers ──────────────────────────────────────────────────────
367
368    fn parse_stack_info_body(r: &ParcelReader<'_>) -> Result<Option<String>, CoreError> {
369        r.skip_i32s(5)?;
370        r.skip_int_array()?;
371        r.read_first_package_from_names()
372    }
373
374    // RootTaskInfo → (taskId, first childTaskName package). Walks the parcel
375    // once: prefix (bounds, childTaskIds), captures the first package from
376    // childTaskNames, then skips childTaskBounds / childTaskUserIds / visible /
377    // position / TaskInfo.userId to reach taskId — never touching the
378    // Intent/TaskInfo tail. taskId and pkg come from the same transaction, so
379    // callers pair them without a second (racy) round-trip.
380    fn parse_root_task_info_task(r: &ParcelReader<'_>) -> Result<(i32, Option<String>), CoreError> {
381        let scratch = r.read_i32()?;
382        if scratch != 0 { r.skip_i32s(4)?; }
383        r.skip_int_array()?; // childTaskIds
384        let pkg = r.read_first_package_from_names()?; // childTaskNames → pkg
385        // childTaskBounds: typed Rect array (nullable) — read count, skip 4 per entry
386        let bounds_count = r.read_i32()?;
387        let n = if bounds_count < 0 { 0 } else { bounds_count as usize };
388        for _ in 0..n {
389            let entry = r.read_i32()?;
390            if entry != 0 { r.skip_i32s(4)?; }
391        }
392        r.skip_int_array()?; // childTaskUserIds
393        r.skip_i32s(2)?;     // visible, position
394        r.skip_i32s(1)?;     // TaskInfo.userId
395        let task_id = r.read_i32()?;
396        Ok((task_id, pkg))
397    }
398
399    // ── Tx code resolution ────────────────────────────────────────────────────
400
401    pub struct TxCodes {
402        pub observer_code: u32,
403        pub query_code:    u32,
404        pub api_mode:      u8,  // 1 = RootTaskInfo, 2 = StackInfo
405        pub fg_code:       u32,
406    }
407
408    pub fn resolve_tx_codes() -> Result<TxCodes, CoreError> {
409        let (obs, query, api, fg) = dex::resolve_tx_codes_from_dex()
410            .ok_or_else(|| CoreError::binder(-1, "tx_code_resolution:dex_parse_failed"))?;
411        Ok(TxCodes { observer_code: obs, query_code: query, api_mode: api, fg_code: fg })
412    }
413
414    // ── ActivityManagerBinder ─────────────────────────────────────────────────
415
416    pub struct ActivityManagerBinder {
417        _lib:    DlHandle,
418        vt:      Vtable,
419        _class:  *mut AIBinder_Class,
420        service: OwnedBinder,
421        tx_code: u32,
422        legacy:  bool,
423    }
424    unsafe impl Send for ActivityManagerBinder {}
425
426    impl ActivityManagerBinder {
427        fn open_inner(handle: *mut c_void) -> Result<(DlHandle, Vtable, *mut AIBinder_Class, OwnedBinder), CoreError> {
428            let lib = DlHandle(handle);
429            let vt = load_vtable(handle)?;
430
431            let am_class = unsafe {
432                (vt.class_define)(
433                    AM_DESCRIPTOR.as_ptr() as *const c_char,
434                    am_on_create, am_on_destroy, am_on_transact,
435                )
436            };
437            if am_class.is_null() { return Err(CoreError::binder(-1, "AIBinder_Class_define:AM")); }
438
439            let raw = unsafe { (vt.get_service)(ACTIVITY_SERVICE.as_ptr() as *const c_char) };
440            if raw.is_null() { return Err(CoreError::binder(-1, "AServiceManager_getService:activity")); }
441            unsafe { (vt.associate_class)(raw, am_class) };
442
443            let service = OwnedBinder { ptr: raw, dec_strong: vt.dec_strong };
444            Ok((lib, vt, am_class, service))
445        }
446
447        fn dlopen_libbinder() -> Result<*mut c_void, CoreError> {
448            use std::os::raw::c_char;
449            let handle = unsafe {
450                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
451            };
452            if handle.is_null() { return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so")); }
453            Ok(handle)
454        }
455
456        /// Open ActivityManager binder (polling mode — no observer).
457        /// Resolves the query tx code from cache or DEX.
458        pub fn open() -> Result<Self, CoreError> {
459            let handle = Self::dlopen_libbinder()?;
460            let (lib, vt, class, service) = Self::open_inner(handle)?;
461            let codes = resolve_tx_codes()?;
462            let legacy = codes.api_mode == 2;
463            Ok(Self { _lib: lib, vt, _class: class, service, tx_code: codes.query_code, legacy })
464        }
465
466        /// Open ActivityManager binder and register as IProcessObserver.
467        ///
468        /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
469        /// callback fd. It becomes readable whenever `onForegroundActivitiesChanged`
470        /// fires. Caller must add it to epoll and may close it at any time — the
471        /// callback keeps writing to the core's copy, so closing the returned
472        /// fd never invalidates the notification path (C2). After the event
473        /// fires, call `get_focused_package`.
474        pub fn open_with_observer() -> Result<(Self, OwnedFd), CoreError> {
475            let handle = Self::dlopen_libbinder()?;
476            let (lib, vt, am_class, service) = Self::open_inner(handle)?;
477            let codes = resolve_tx_codes()?;
478            let legacy = codes.api_mode == 2;
479
480            // Create eventfd for callback → epoll bridge. Ownership stays in the
481            // core for the observer lifetime; the consumer receives a dup below.
482            let owned = unsafe {
483                let raw = libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC);
484                if raw < 0 { return Err(CoreError::sys(*libc::__errno(), "eventfd")); }
485                OwnedFd::from_raw_fd(raw)
486            };
487
488            // Define IProcessObserver class (we're the server)
489            let obs_class = unsafe {
490                (vt.class_define)(
491                    OBS_DESCRIPTOR.as_ptr() as *const c_char,
492                    obs_on_create, obs_on_destroy, obs_on_transact,
493                )
494            };
495            if obs_class.is_null() {
496                return Err(CoreError::binder(-1, "AIBinder_Class_define:Observer"));
497            }
498
499            // Instantiate our observer binder object
500            let obs_binder = unsafe { (vt.new_binder)(obs_class, std::ptr::null_mut()) };
501            if obs_binder.is_null() {
502                return Err(CoreError::binder(-1, "AIBinder_new:Observer"));
503            }
504            unsafe { (vt.associate_class)(obs_binder, obs_class) };
505
506            // Call registerProcessObserver(observer)
507            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
508            let s = unsafe { (vt.prepare_transaction)(service.ptr, &mut in_ptr) };
509            if s != STATUS_OK {
510                return Err(CoreError::binder(s, "prepareTransaction:registerObserver"));
511            }
512            unsafe { (vt.write_strong_binder)(in_ptr, obs_binder) };
513            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
514            let s = unsafe {
515                (vt.transact)(service.ptr, codes.observer_code, &mut in_ptr, &mut out_ptr, 0)
516            };
517            if !out_ptr.is_null() { unsafe { (vt.parcel_delete)(out_ptr) }; }
518            if s != STATUS_OK {
519                return Err(CoreError::binder(s, "transact:registerProcessObserver"));
520            }
521
522            // Consumer dup — made before publishing, so an error path drops the
523            // owned fd without ever leaving a stale handle for the callback.
524            let consumer = owned.try_clone()
525                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:observer"))?;
526
527            // Publish fg_code and the core-owned eventfd for the callback
528            OBS_FG_CODE.store(codes.fg_code, Ordering::Relaxed);
529            *obs_eventfd_guard() = Some(owned);
530
531            // Start binder thread pool — blocks forever in background thread
532            unsafe { (vt.set_thread_pool_max)(0) };
533            let join_fn = vt.join_thread_pool;
534            std::thread::spawn(move || unsafe { join_fn() });
535
536            let binder = Self { _lib: lib, vt, _class: am_class, service, tx_code: codes.query_code, legacy };
537            Ok((binder, consumer))
538        }
539
540        /// Open ActivityManager binder and register as the foreground process
541        /// observer.
542        ///
543        /// The authoritative foreground PID is delivered in the callback; this
544        /// is the low-noise foreground source. Two ROM variants are supported
545        /// and selected automatically:
546        ///
547        /// - Stock: `IForegroundProcessObserver.onForegroundProcessChanged`
548        ///   delivers a single `int pid`.
549        /// - Custom ROMs that dropped that interface instead deliver `(int pid,
550        ///   int uid, int fg)` through the repurposed
551        ///   `IProcessObserver.onForegroundActivitiesChanged`; this registers
552        ///   via `registerProcessObserver` and only signals on `fg != 0`.
553        ///
554        /// The callback stores the PID (readable via [`last_foreground_pid`])
555        /// and signals the returned eventfd.
556        ///
557        /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
558        /// callback fd. It becomes readable whenever a foreground process
559        /// change fires. Same lifetime contract as
560        /// [`ActivityManagerBinder::open_with_observer`] (C2): the core owns
561        /// the eventfd and the callback only ever writes to that copy, so
562        /// closing the returned dup never invalidates the notification path.
563        pub fn open_with_fgproc_observer() -> Result<(Self, OwnedFd), CoreError> {
564            let handle = Self::dlopen_libbinder()?;
565            let (lib, vt, am_class, service) = Self::open_inner(handle)?;
566
567            // Resolve the foreground-observer tx codes. Prefer the stock
568            // IForegroundProcessObserver path; fall back to the custom
569            // IProcessObserver pid-carrying form on ROMs that dropped it.
570            // mode 0 = stock single-int callback, mode 1 = (pid, uid, fg).
571            let (register_code, fgproc_code, mode, descriptor): (u32, u32, u32, &[u8]) =
572                match crate::dex::resolve_fgproc_codes() {
573                    Some((r, c)) => (r, c, 0, FGPROC_DESCRIPTOR),
574                    None => match crate::dex::resolve_fgproc_codes_fallback() {
575                        Some((r, c)) => (r, c, 1, OBS_DESCRIPTOR),
576                        None => {
577                            return Err(CoreError::binder(-1, "tx_code_resolution:fgproc_dex_parse_failed"));
578                        }
579                    },
580                };
581
582            // Create eventfd for callback → epoll bridge. Ownership stays in the
583            // core for the observer lifetime; the consumer receives a dup below.
584            let owned = unsafe {
585                let raw = libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC);
586                if raw < 0 { return Err(CoreError::sys(*libc::__errno(), "eventfd")); }
587                OwnedFd::from_raw_fd(raw)
588            };
589
590            // Define our observer class (we're the server). The descriptor must
591            // match whichever interface we actually register as.
592            let obs_class = unsafe {
593                (vt.class_define)(
594                    descriptor.as_ptr() as *const c_char,
595                    fgproc_on_create, fgproc_on_destroy, fgproc_on_transact,
596                )
597            };
598            if obs_class.is_null() {
599                return Err(CoreError::binder(-1, "AIBinder_Class_define:FGProcessObserver"));
600            }
601
602            // Instantiate our observer binder object
603            let obs_binder = unsafe { (vt.new_binder)(obs_class, std::ptr::null_mut()) };
604            if obs_binder.is_null() {
605                return Err(CoreError::binder(-1, "AIBinder_new:FGProcessObserver"));
606            }
607            unsafe { (vt.associate_class)(obs_binder, obs_class) };
608
609            // Call registerForegroundProcessObserver(observer) or the fallback
610            // registerProcessObserver(observer) depending on resolved mode.
611            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
612            let s = unsafe { (vt.prepare_transaction)(service.ptr, &mut in_ptr) };
613            if s != STATUS_OK {
614                return Err(CoreError::binder(s, "prepareTransaction:registerForegroundProcessObserver"));
615            }
616            unsafe { (vt.write_strong_binder)(in_ptr, obs_binder) };
617            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
618            let s = unsafe {
619                (vt.transact)(service.ptr, register_code, &mut in_ptr, &mut out_ptr, 0)
620            };
621            if !out_ptr.is_null() { unsafe { (vt.parcel_delete)(out_ptr) }; }
622            if s != STATUS_OK {
623                return Err(CoreError::binder(s, "transact:registerForegroundProcessObserver"));
624            }
625
626            // Consumer dup — made before publishing, so an error path drops the
627            // owned fd without ever leaving a stale handle for the callback.
628            let consumer = owned.try_clone()
629                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:fgproc_observer"))?;
630
631            // Publish reader fn, mode, fg code, pid base, and the core-owned
632            // eventfd for the callback. Reader and mode are published first so
633            // the callback never sees a matching code with an unset reader or
634            // mode (C2-adjacent init order).
635            FGPROC_READ_I32.store(vt.read_int32 as usize, Ordering::Relaxed);
636            FGPROC_IPROC_MODE.store(mode, Ordering::Relaxed);
637            FGPROC_FG_CODE.store(fgproc_code, Ordering::Relaxed);
638            FGPROC_PID.store(0, Ordering::Relaxed);
639            *fgproc_eventfd_guard() = Some(owned);
640
641            // Start binder thread pool — blocks forever in background thread
642            unsafe { (vt.set_thread_pool_max)(0) };
643            let join_fn = vt.join_thread_pool;
644            std::thread::spawn(move || unsafe { join_fn() });
645
646            let binder = Self { _lib: lib, vt, _class: am_class, service, tx_code: 0, legacy: false };
647            Ok((binder, consumer))
648        }
649
650        fn do_transact(&self) -> Result<OwnedParcel, CoreError> {
651            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
652            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut in_ptr) };
653            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
654            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
655            let s = unsafe {
656                (self.vt.transact)(self.service.ptr, self.tx_code, &mut in_ptr, &mut out_ptr, 0)
657            };
658            let out = OwnedParcel { ptr: out_ptr, delete: self.vt.parcel_delete };
659            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
660            Ok(out)
661        }
662
663        /// The focused root task's `(taskId, topActivity package)` from a single
664        /// txn-31 transaction. Outer `None` = no focused root task (or legacy
665        /// API 29, where the reply is `StackInfo` and carries no taskId); inner
666        /// `None` = task known but no package in `childTaskNames`. Both values
667        /// come from the same parcel, so the registration key and the report
668        /// tag can never diverge.
669        pub fn get_focused_task(&self) -> Result<Option<(i32, Option<String>)>, CoreError> {
670            if self.legacy {
671                // StackInfo has no taskId — report None rather than a wrong id.
672                return Ok(None);
673            }
674            let out = self.do_transact()?;
675            let r = ParcelReader { vt: &self.vt, parcel: &out };
676            let ex = r.read_i32()?;
677            if ex != EX_NONE { return Err(CoreError::binder(ex, "getFocusedTask:exception")); }
678            let present = r.read_i32()?;
679            if present == 0 { return Ok(None); }
680            Ok(Some(parse_root_task_info_task(&r)?))
681        }
682
683        /// The `topActivity` package of the focused root task (legacy API 29
684        /// builds use `StackInfo` and still resolve the package). Thin wrapper
685        /// over [`ActivityManagerBinder::get_focused_task`].
686        pub fn get_focused_package(&self) -> Result<Option<String>, CoreError> {
687            if self.legacy {
688                let out = self.do_transact()?;
689                let r = ParcelReader { vt: &self.vt, parcel: &out };
690                let ex = r.read_i32()?;
691                if ex != EX_NONE { return Err(CoreError::binder(ex, "getFocusedTask:exception")); }
692                let present = r.read_i32()?;
693                if present == 0 { return Ok(None); }
694                return parse_stack_info_body(&r);
695            }
696            Ok(self.get_focused_task()?.map(|(_, pkg)| pkg).flatten())
697        }
698
699        /// The `taskId` of the currently focused root task. Thin wrapper over
700        /// [`ActivityManagerBinder::get_focused_task`]; returns `None` when there
701        /// is no focused root task or on legacy API 29 builds.
702        pub fn get_focused_task_id(&self) -> Result<Option<i32>, CoreError> {
703            Ok(self.get_focused_task()?.map(|(task_id, _)| task_id))
704        }
705    }
706
707    // ── DisplayManagerBinder ─────────────────────────────────────────────────
708
709    const DISPLAY_SERVICE:    &[u8] = b"display\0";
710    const DISPLAY_DESCRIPTOR: &[u8] = b"android.hardware.display.IDisplayManager\0";
711    const CALLBACK_DESCRIPTOR: &[u8] = b"android.hardware.display.IDisplayManagerCallback\0";
712    const POWER_SERVICE:      &[u8] = b"power\0";
713
714    const TX_DISPLAY_REGISTER_CALLBACK: u32 = 4;
715
716    // Core owns the callback eventfd; the consumer gets a dup and may close it
717    // freely. Same lifetime discipline as the ActivityManager observer (C2).
718    static DISP_EVENTFD: Mutex<Option<OwnedFd>> = Mutex::new(None);
719
720    fn disp_eventfd_guard() -> std::sync::MutexGuard<'static, Option<OwnedFd>> {
721        DISP_EVENTFD.lock().unwrap_or_else(|p| p.into_inner())
722    }
723
724    unsafe extern "C" fn disp_cb_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
725    unsafe extern "C" fn disp_cb_on_destroy(_: *mut c_void) {}
726    unsafe extern "C" fn disp_cb_on_transact(
727        _: *mut AIBinder, code: u32, _: *const AParcel, _: *mut AParcel,
728    ) -> BinderStatus {
729        if code == 1 {
730            if let Some(fd) = disp_eventfd_guard().as_ref() {
731                let val: u64 = 1;
732                unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
733            }
734        }
735        STATUS_OK
736    }
737
738    pub struct DisplayManagerBinder {
739        _lib:           DlHandle,
740        vt:             Vtable,
741        display:        OwnedBinder,
742        power:          Option<OwnedBinder>,
743        is_interactive_tx: u32,
744    }
745    unsafe impl Send for DisplayManagerBinder {}
746
747    impl DisplayManagerBinder {
748        pub fn open_with_callback() -> Result<(Self, crate::reactor::Fd), CoreError> {
749            let handle = unsafe {
750                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
751            };
752            if handle.is_null() { return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so")); }
753            let lib = DlHandle(handle);
754            let vt = load_vtable(handle)?;
755
756            // Blocking eventfd (no EFD_NONBLOCK) — callback writes, caller's
757            // read_u64_blocking() waits. The core owns it for the callback's
758            // lifetime; the consumer receives a dup below (C2).
759            let owned = unsafe {
760                let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
761                if raw < 0 { return Err(CoreError::sys(*libc::__errno(), "eventfd")); }
762                OwnedFd::from_raw_fd(raw)
763            };
764
765            // Get display service (no class_define needed for client-only)
766            let raw_display = unsafe { (vt.get_service)(DISPLAY_SERVICE.as_ptr() as *const c_char) };
767            if raw_display.is_null() {
768                return Err(CoreError::binder(-1, "AServiceManager_getService:display"));
769            }
770            let display = OwnedBinder { ptr: raw_display, dec_strong: vt.dec_strong };
771
772            // Define IDisplayManagerCallback (we're the server receiving callbacks)
773            let cb_class = unsafe {
774                (vt.class_define)(
775                    CALLBACK_DESCRIPTOR.as_ptr() as *const c_char,
776                    disp_cb_on_create, disp_cb_on_destroy, disp_cb_on_transact,
777                )
778            };
779            if cb_class.is_null() {
780                return Err(CoreError::binder(-1, "AIBinder_Class_define:DisplayCallback"));
781            }
782
783            let cb_binder = unsafe { (vt.new_binder)(cb_class, std::ptr::null_mut()) };
784            if cb_binder.is_null() {
785                return Err(CoreError::binder(-1, "AIBinder_new:DisplayCallback"));
786            }
787
788            // registerCallback(callback) — tx 4
789            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
790            let s = unsafe { (vt.prepare_transaction)(display.ptr, &mut in_ptr) };
791            if s != STATUS_OK {
792                return Err(CoreError::binder(s, "prepareTransaction:registerCallback"));
793            }
794            unsafe { (vt.write_strong_binder)(in_ptr, cb_binder) };
795            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
796            let s = unsafe {
797                (vt.transact)(display.ptr, TX_DISPLAY_REGISTER_CALLBACK, &mut in_ptr, &mut out_ptr, 0)
798            };
799            if !out_ptr.is_null() { unsafe { (vt.parcel_delete)(out_ptr) }; }
800            if s != STATUS_OK {
801                return Err(CoreError::binder(s, "transact:registerCallback"));
802            }
803
804            // Optional: grab power service for is_interactive()
805            let power = {
806                let raw = unsafe { (vt.get_service)(POWER_SERVICE.as_ptr() as *const c_char) };
807                if raw.is_null() { None } else { Some(OwnedBinder { ptr: raw, dec_strong: vt.dec_strong }) }
808            };
809
810            // Resolve isInteractive tx code from DEX at open time
811            let is_interactive_tx = crate::dex::resolve_is_interactive_tx()
812                .ok_or_else(|| CoreError::binder(-1, "dex:TRANSACTION_isInteractive not found"))?;
813
814            // Consumer dup — made before publishing, so an error path drops the
815            // owned fd without ever leaving a stale handle for the callback.
816            let efd_owned = owned.try_clone()
817                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:display"))
818                .and_then(|dup| unsafe {
819                    crate::reactor::Fd::from_owned_raw_fd(dup.into_raw_fd(), "display.efd")
820                        .map_err(|_| CoreError::binder(-1, "Fd::from_owned_raw_fd:display.efd"))
821                })?;
822
823            // Publish the core-owned eventfd for the callback
824            *disp_eventfd_guard() = Some(owned);
825
826            // Join binder thread pool so callbacks can fire
827            unsafe { (vt.set_thread_pool_max)(0) };
828            let join_fn = vt.join_thread_pool;
829            std::thread::spawn(move || unsafe { join_fn() });
830
831            Ok((Self { _lib: lib, vt, display, power, is_interactive_tx }, efd_owned))
832        }
833
834        pub fn is_interactive(&self) -> Result<bool, CoreError> {
835            let power = self.power.as_ref()
836                .ok_or_else(|| CoreError::binder(-1, "power:unavailable"))?;
837            let mut inp: *mut AParcel = std::ptr::null_mut();
838            let s = unsafe { (self.vt.prepare_transaction)(power.ptr, &mut inp) };
839            if s != STATUS_OK { return Err(CoreError::binder(s, "prepareTransaction:isInteractive")); }
840            let mut out: *mut AParcel = std::ptr::null_mut();
841            let s = unsafe {
842                (self.vt.transact)(power.ptr, self.is_interactive_tx, &mut inp, &mut out, 0)
843            };
844            let out = OwnedParcel { ptr: out, delete: self.vt.parcel_delete };
845            if s != STATUS_OK { return Err(CoreError::binder(s, "transact:isInteractive")); }
846            let r = ParcelReader { vt: &self.vt, parcel: &out };
847            let ex = r.read_i32()?;
848            if ex != EX_NONE { return Err(CoreError::binder(ex, "isInteractive:exception")); }
849            if let Some(rb) = self.vt.read_bool {
850                let mut v = false;
851                let s = unsafe { rb(out.ptr as *const AParcel, &mut v) };
852                if s != STATUS_OK { return Err(CoreError::binder(s, "readBool:isInteractive")); }
853                Ok(v)
854            } else {
855                Ok(r.read_i32()? != 0)
856            }
857        }
858    }
859
860    // ── FpsListener (task FPS callback) ───────────────────────────────────────
861
862    const WINDOW_SERVICE:    &[u8] = b"window\0";
863    const WM_DESCRIPTOR:     &[u8] = b"android.view.IWindowManager\0";
864    const FPS_DESCRIPTOR:    &[u8] = b"android.window.ITaskFpsCallback\0";
865    // Core owns the callback eventfd; the consumer gets a dup and may close it
866    // freely. Same lifetime discipline as the other callbacks (C2). The last
867    // reported FPS (bit pattern of the f32) is published before the eventfd is
868    // signalled, so the consumer never reads a stale value.
869    static FPS_EVENTFD: Mutex<Option<OwnedFd>> = Mutex::new(None);
870    static FPS_VALUE: AtomicU32 = AtomicU32::new(0);
871    static FPS_CODE: AtomicU32 = AtomicU32::new(0);
872    static FPS_READ_I32: AtomicUsize = AtomicUsize::new(0);
873
874    fn fps_eventfd_guard() -> std::sync::MutexGuard<'static, Option<OwnedFd>> {
875        FPS_EVENTFD.lock().unwrap_or_else(|p| p.into_inner())
876    }
877
878    // No-op callbacks for the client-only IWindowManager class (we never serve
879    // transactions on the `window` binder — the class exists only to satisfy
880    // AIBinder_prepareTransaction's remote-transaction contract).
881    unsafe extern "C" fn wm_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
882    unsafe extern "C" fn wm_on_destroy(_: *mut c_void) {}
883    unsafe extern "C" fn wm_on_transact(
884        _: *mut AIBinder, _: u32, _: *const AParcel, _: *mut AParcel,
885    ) -> BinderStatus { STATUS_OK }
886
887    unsafe extern "C" fn fps_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
888    unsafe extern "C" fn fps_on_destroy(_: *mut c_void) {}
889    unsafe extern "C" fn fps_on_transact(
890        _: *mut AIBinder, code: u32, in_parcel: *const AParcel, _: *mut AParcel,
891    ) -> BinderStatus {
892        if code != FPS_CODE.load(Ordering::Relaxed) {
893            return STATUS_UNKNOWN_TRANSACTION;
894        }
895        // Reader is published non-zero before the code, so a matching code is
896        // never paired with an unset reader.
897        let read_addr = FPS_READ_I32.load(Ordering::Relaxed);
898        if read_addr != 0 {
899            let read_fn: unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus =
900                unsafe { std::mem::transmute(read_addr) };
901            let mut bits: i32 = 0;
902            if unsafe { read_fn(in_parcel, &mut bits) } == STATUS_OK {
903                // Publish the value before signalling so the consumer always
904                // sees the value that triggered the wakeup.
905                FPS_VALUE.store(bits as u32, Ordering::Relaxed);
906                if let Some(fd) = fps_eventfd_guard().as_ref() {
907                    let val: u64 = 1;
908                    unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
909                }
910            }
911        }
912        STATUS_OK
913    }
914
915    /// Push-based per-task FPS listener registered with `WindowManager`.
916    ///
917    /// Uses `IWindowManager.registerTaskFpsCallback(taskId, callback)`; the
918    /// daemon hosts the `ITaskFpsCallback` server object and receives
919    /// `onFpsReported(float)` one-way transactions from the `FpsReporter` at
920    /// most every ~500 ms.
921    ///
922    /// The registering UID must hold `ACCESS_FPS_COUNTER` (signature|privileged)
923    /// — this process typically runs as shell (uid 2000) via `su`.
924    ///
925    /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
926    /// callback fd. It becomes readable whenever `onFpsReported` fires; call
927    /// [`FpsListener::last_fps`] after the event to read the value.
928    pub struct FpsListener {
929        _lib:       DlHandle,
930        vt:         Vtable,
931        window:     OwnedBinder,
932        cb_binder:  *mut AIBinder,
933        _wm_class:  *mut AIBinder_Class,
934        register_code: u32,
935        unregister_code: u32,
936        task_id:    i32,
937    }
938    unsafe impl Send for FpsListener {}
939
940    impl FpsListener {
941        /// Open WindowManager and define the `ITaskFpsCallback` server object.
942        ///
943        /// Resolves the three tx codes from DEX. Does **not** register a task
944        /// yet — call [`FpsListener::register`] once a taskId is known. Starts
945        /// the binder thread pool so `onFpsReported` can fire.
946        pub fn open() -> Result<(Self, OwnedFd), CoreError> {
947            let handle = unsafe {
948                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
949            };
950            if handle.is_null() { return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so")); }
951            let lib = DlHandle(handle);
952            let vt = load_vtable(handle)?;
953
954            let (register_code, unregister_code, on_fps_code) =
955                crate::dex::resolve_fps_codes()
956                    .ok_or_else(|| CoreError::binder(-1, "dex:TRANSACTION_registerTaskFpsCallback not found"))?;
957
958            let window = {
959                let raw = unsafe { (vt.get_service)(WINDOW_SERVICE.as_ptr() as *const c_char) };
960                if raw.is_null() { return Err(CoreError::binder(-1, "AServiceManager_getService:window")); }
961                OwnedBinder { ptr: raw, dec_strong: vt.dec_strong }
962            };
963
964            // Remote transactions require a class on the binder (same
965            // AIBinder_prepareTransaction contract as the AM service above).
966            let wm_class = unsafe {
967                (vt.class_define)(
968                    WM_DESCRIPTOR.as_ptr() as *const c_char,
969                    wm_on_create, wm_on_destroy, wm_on_transact,
970                )
971            };
972            if wm_class.is_null() {
973                return Err(CoreError::binder(-1, "AIBinder_Class_define:IWindowManager"));
974            }
975            unsafe { (vt.associate_class)(window.ptr, wm_class) };
976
977            let cb_class = unsafe {
978                (vt.class_define)(
979                    FPS_DESCRIPTOR.as_ptr() as *const c_char,
980                    fps_on_create, fps_on_destroy, fps_on_transact,
981                )
982            };
983            if cb_class.is_null() {
984                return Err(CoreError::binder(-1, "AIBinder_Class_define:ITaskFpsCallback"));
985            }
986
987            let cb_binder = unsafe { (vt.new_binder)(cb_class, std::ptr::null_mut()) };
988            if cb_binder.is_null() {
989                return Err(CoreError::binder(-1, "AIBinder_new:ITaskFpsCallback"));
990            }
991            unsafe { (vt.associate_class)(cb_binder, cb_class) };
992
993            // Blocking eventfd — callback writes, consumer waits/reads. Core
994            // owns it; the consumer receives a dup below (C2).
995            let owned = unsafe {
996                let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
997                if raw < 0 { return Err(CoreError::sys(*libc::__errno(), "eventfd")); }
998                OwnedFd::from_raw_fd(raw)
999            };
1000
1001            let consumer = owned.try_clone()
1002                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:fps"))?;
1003
1004            // Publish reader, code, and eventfd — in that order so the callback
1005            // never sees a matching code with an unset reader or fd (C2-adjacent).
1006            FPS_READ_I32.store(vt.read_int32 as usize, Ordering::Relaxed);
1007            FPS_CODE.store(on_fps_code, Ordering::Relaxed);
1008            FPS_VALUE.store(0, Ordering::Relaxed);
1009            *fps_eventfd_guard() = Some(owned);
1010
1011            unsafe { (vt.set_thread_pool_max)(0) };
1012            let join_fn = vt.join_thread_pool;
1013            std::thread::spawn(move || unsafe { join_fn() });
1014
1015            Ok((Self {
1016                _lib: lib, vt, window, cb_binder, _wm_class: wm_class,
1017                register_code, unregister_code, task_id: -1,
1018            }, consumer))
1019        }
1020
1021        /// Register the callback for `task_id`. If a task was already
1022        /// registered, it is unregistered first (WindowManager tracks one task
1023        /// per callback binder).
1024        pub fn register(&mut self, task_id: i32) -> Result<(), CoreError> {
1025            if self.task_id == task_id {
1026                return Ok(());
1027            }
1028            if self.task_id >= 0 {
1029                let _ = self.unregister();
1030            }
1031
1032            let mut inp: *mut AParcel = std::ptr::null_mut();
1033            let s = unsafe { (self.vt.prepare_transaction)(self.window.ptr, &mut inp) };
1034            if s != STATUS_OK { return Err(CoreError::binder(s, "prepareTransaction:registerTaskFpsCallback")); }
1035            let s = unsafe { (self.vt.write_int32)(inp, task_id) };
1036            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_writeInt32:taskId")); }
1037            let s = unsafe { (self.vt.write_strong_binder)(inp, self.cb_binder) };
1038            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_writeStrongBinder")); }
1039            let mut out: *mut AParcel = std::ptr::null_mut();
1040            let s = unsafe {
1041                (self.vt.transact)(self.window.ptr, self.register_code, &mut inp, &mut out, 0)
1042            };
1043            if !out.is_null() { unsafe { (self.vt.parcel_delete)(out) }; }
1044            if s != STATUS_OK {
1045                return Err(CoreError::binder(s, "transact:registerTaskFpsCallback"));
1046            }
1047            self.task_id = task_id;
1048            Ok(())
1049        }
1050
1051        /// Unregister the callback from WindowManager. No-op if nothing is
1052        /// registered.
1053        pub fn unregister(&mut self) -> Result<(), CoreError> {
1054            if self.task_id < 0 {
1055                return Ok(());
1056            }
1057            let mut inp: *mut AParcel = std::ptr::null_mut();
1058            let s = unsafe { (self.vt.prepare_transaction)(self.window.ptr, &mut inp) };
1059            if s != STATUS_OK { return Err(CoreError::binder(s, "prepareTransaction:unregisterTaskFpsCallback")); }
1060            let s = unsafe { (self.vt.write_strong_binder)(inp, self.cb_binder) };
1061            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_writeStrongBinder")); }
1062            let mut out: *mut AParcel = std::ptr::null_mut();
1063            let s = unsafe {
1064                (self.vt.transact)(self.window.ptr, self.unregister_code, &mut inp, &mut out, 0)
1065            };
1066            if !out.is_null() { unsafe { (self.vt.parcel_delete)(out) }; }
1067            if s != STATUS_OK {
1068                return Err(CoreError::binder(s, "transact:unregisterTaskFpsCallback"));
1069            }
1070            self.task_id = -1;
1071            Ok(())
1072        }
1073
1074        /// The most recent `onFpsReported` value (f32), or `None` if no report
1075        /// has arrived yet. Safe to call at any time; the bit pattern is
1076        /// published atomically.
1077        pub fn last_fps(&self) -> Option<f32> {
1078            let bits = FPS_VALUE.load(Ordering::Relaxed);
1079            if bits == 0 { None } else { Some(f32::from_bits(bits)) }
1080        }
1081
1082        /// The taskId currently registered, or `None` if none.
1083        pub fn task_id(&self) -> Option<i32> {
1084            (self.task_id >= 0).then_some(self.task_id)
1085        }
1086    }
1087
1088    // ── RawBinderService ──────────────────────────────────────────────────────
1089
1090    /// Generic binder client for any named Android service.
1091    ///
1092    /// Handles its own `dlopen` on `libbinder_ndk.so`. Callers provide raw
1093    /// transaction codes (resolved via [`crate::dex::find_transaction_code`])
1094    /// and use [`RawBinderService::transact_bool`] /
1095    /// [`RawBinderService::transact_i32`] for typed round-trips.
1096    pub struct RawBinderService {
1097        _lib:    DlHandle,
1098        vt:      Vtable,
1099        service: OwnedBinder,
1100    }
1101    unsafe impl Send for RawBinderService {}
1102
1103    impl RawBinderService {
1104        /// Open a connection to the named service (e.g. `"power"`, `"batterystats"`).
1105        pub fn open(service_name: &str) -> Result<Self, CoreError> {
1106            use std::ffi::CString;
1107            let handle = unsafe {
1108                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
1109            };
1110            if handle.is_null() {
1111                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
1112            }
1113            let lib = DlHandle(handle);
1114            let vt = load_vtable(handle)?;
1115            let cs = CString::new(service_name)
1116                .map_err(|_| CoreError::binder(-1, "service_name:nul_byte"))?;
1117            let raw = unsafe { (vt.get_service)(cs.as_ptr()) };
1118            if raw.is_null() {
1119                return Err(CoreError::binder(-1, "AServiceManager_getService:null"));
1120            }
1121            let service = OwnedBinder { ptr: raw, dec_strong: vt.dec_strong };
1122            Ok(Self { _lib: lib, vt, service })
1123        }
1124
1125        /// Send a no-argument transaction; read exception header then bool reply.
1126        pub fn transact_bool(&self, code: u32) -> Result<bool, CoreError> {
1127            let out = self.raw_noarg(code)?;
1128            let r = ParcelReader { vt: &self.vt, parcel: &out };
1129            let ex = r.read_i32()?;
1130            if ex != EX_NONE { return Err(CoreError::binder(ex, "transact_bool:exception")); }
1131            if let Some(rb) = self.vt.read_bool {
1132                let mut v = false;
1133                let s = unsafe { rb(out.ptr as *const AParcel, &mut v) };
1134                if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readBool")); }
1135                Ok(v)
1136            } else {
1137                Ok(r.read_i32()? != 0)
1138            }
1139        }
1140
1141        /// Send a transaction with one i32 argument; discard reply.
1142        pub fn transact_i32(&self, code: u32, arg: i32) -> Result<(), CoreError> {
1143            let mut inp: *mut AParcel = std::ptr::null_mut();
1144            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut inp) };
1145            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
1146            let s = unsafe { (self.vt.write_int32)(inp, arg) };
1147            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_writeInt32")); }
1148            let mut out: *mut AParcel = std::ptr::null_mut();
1149            let s = unsafe { (self.vt.transact)(self.service.ptr, code, &mut inp, &mut out, 0) };
1150            if !out.is_null() { unsafe { (self.vt.parcel_delete)(out) }; }
1151            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
1152            Ok(())
1153        }
1154
1155        fn raw_noarg(&self, code: u32) -> Result<OwnedParcel, CoreError> {
1156            let mut inp: *mut AParcel = std::ptr::null_mut();
1157            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut inp) };
1158            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
1159            let mut out: *mut AParcel = std::ptr::null_mut();
1160            let s = unsafe { (self.vt.transact)(self.service.ptr, code, &mut inp, &mut out, 0) };
1161            let out = OwnedParcel { ptr: out, delete: self.vt.parcel_delete };
1162            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
1163            Ok(out)
1164        }
1165    }
1166}
1167
1168// ── Public re-exports ─────────────────────────────────────────────────────────
1169
1170#[cfg(target_os = "android")]
1171pub use imp::{ActivityManagerBinder, DisplayManagerBinder, FpsListener, RawBinderService, TxCodes, last_foreground_pid, resolve_tx_codes};
1172
1173// ── Non-Android stubs ─────────────────────────────────────────────────────────
1174
1175#[cfg(not(target_os = "android"))]
1176pub struct ActivityManagerBinder;
1177
1178#[cfg(not(target_os = "android"))]
1179impl ActivityManagerBinder {
1180    pub fn open() -> Result<Self, crate::CoreError> {
1181        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1182    }
1183    pub fn open_with_observer() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1184        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1185    }
1186    pub fn open_with_fgproc_observer() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1187        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1188    }
1189    pub fn get_focused_task(&self) -> Result<Option<(i32, Option<String>)>, crate::CoreError> {
1190        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1191    }
1192    pub fn get_focused_package(&self) -> Result<Option<String>, crate::CoreError> {
1193        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1194    }
1195    pub fn get_focused_task_id(&self) -> Result<Option<i32>, crate::CoreError> {
1196        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1197    }
1198}
1199
1200#[cfg(not(target_os = "android"))]
1201pub struct DisplayManagerBinder;
1202
1203#[cfg(not(target_os = "android"))]
1204impl DisplayManagerBinder {
1205    pub fn open_with_callback() -> Result<(Self, crate::reactor::Fd), crate::CoreError> {
1206        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1207    }
1208    pub fn is_interactive(&self) -> Result<bool, crate::CoreError> {
1209        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1210    }
1211}
1212
1213#[cfg(not(target_os = "android"))]
1214pub struct RawBinderService;
1215
1216#[cfg(not(target_os = "android"))]
1217impl RawBinderService {
1218    pub fn open(_service_name: &str) -> Result<Self, crate::CoreError> {
1219        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1220    }
1221    pub fn transact_bool(&self, _code: u32) -> Result<bool, crate::CoreError> {
1222        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1223    }
1224    pub fn transact_i32(&self, _code: u32, _arg: i32) -> Result<(), crate::CoreError> {
1225        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1226    }
1227}
1228
1229#[cfg(not(target_os = "android"))]
1230pub struct FpsListener;
1231
1232#[cfg(not(target_os = "android"))]
1233impl FpsListener {
1234    pub fn open() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1235        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1236    }
1237    pub fn register(&mut self, _task_id: i32) -> Result<(), crate::CoreError> {
1238        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1239    }
1240    pub fn unregister(&mut self) -> Result<(), crate::CoreError> {
1241        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1242    }
1243    pub fn last_fps(&self) -> Option<f32> {
1244        None
1245    }
1246    pub fn task_id(&self) -> Option<i32> {
1247        None
1248    }
1249}
1250
1251#[cfg(not(target_os = "android"))]
1252pub struct TxCodes { pub observer_code: u32, pub query_code: u32, pub api_mode: u8, pub fg_code: u32 }
1253
1254#[cfg(not(target_os = "android"))]
1255pub fn resolve_tx_codes() -> Result<TxCodes, crate::CoreError> {
1256    Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1257}
1258
1259#[cfg(not(target_os = "android"))]
1260pub fn last_foreground_pid() -> i32 {
1261    0
1262}