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        get_user_data:       unsafe extern "C" fn(*const AIBinder) -> *mut c_void,
233        write_int32:         unsafe extern "C" fn(*mut AParcel, i32) -> BinderStatus,
234        // Optional: only present on API 29+, but all modern Android has this
235        read_bool:           Option<unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus>,
236    }
237
238    // ── RAII wrappers ─────────────────────────────────────────────────────────
239
240    struct DlHandle(*mut c_void);
241    unsafe impl Send for DlHandle {}
242    impl Drop for DlHandle {
243        fn drop(&mut self) {
244            // Intentionally no dlclose: the binder thread pool spawned in
245            // open_with_observer() keeps executing library code until process
246            // exit. Unloading the library while that thread runs causes
247            // use-after-free. libbinder_ndk.so is never unloaded during the
248            // daemon lifetime; the OS reclaims it on exit.
249        }
250    }
251
252    struct OwnedParcel { ptr: *mut AParcel, delete: unsafe extern "C" fn(*mut AParcel) }
253    impl Drop for OwnedParcel {
254        fn drop(&mut self) { if !self.ptr.is_null() { unsafe { (self.delete)(self.ptr) }; } }
255    }
256
257    struct OwnedBinder { ptr: *mut AIBinder, dec_strong: unsafe extern "C" fn(*mut AIBinder) }
258    unsafe impl Send for OwnedBinder {}
259    impl Drop for OwnedBinder {
260        fn drop(&mut self) { if !self.ptr.is_null() { unsafe { (self.dec_strong)(self.ptr) }; } }
261    }
262
263    // ── dlsym helper ─────────────────────────────────────────────────────────
264
265    macro_rules! dlsym_fn {
266        ($handle:expr, $name:literal, $ty:ty) => {{
267            let sym = unsafe {
268                libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char)
269            };
270            if sym.is_null() {
271                return Err(CoreError::binder(-1, concat!("dlsym:", $name)));
272            }
273            unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }
274        }};
275    }
276
277    macro_rules! dlsym_opt {
278        ($handle:expr, $name:literal, $ty:ty) => {{
279            let sym = unsafe {
280                libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char)
281            };
282            if sym.is_null() { None }
283            else { Some(unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }) }
284        }};
285    }
286
287    fn load_vtable(handle: *mut c_void) -> Result<Vtable, CoreError> {
288        Ok(Vtable {
289            get_service: dlsym_fn!(handle, "AServiceManager_getService",
290                unsafe extern "C" fn(*const c_char) -> *mut AIBinder),
291            class_define: dlsym_fn!(handle, "AIBinder_Class_define",
292                unsafe extern "C" fn(
293                    *const c_char,
294                    unsafe extern "C" fn(*mut c_void) -> *mut c_void,
295                    unsafe extern "C" fn(*mut c_void),
296                    unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel) -> BinderStatus,
297                ) -> *mut AIBinder_Class),
298            associate_class: dlsym_fn!(handle, "AIBinder_associateClass",
299                unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool),
300            new_binder: dlsym_fn!(handle, "AIBinder_new",
301                unsafe extern "C" fn(*const AIBinder_Class, *mut c_void) -> *mut AIBinder),
302            prepare_transaction: dlsym_fn!(handle, "AIBinder_prepareTransaction",
303                unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus),
304            transact: dlsym_fn!(handle, "AIBinder_transact",
305                unsafe extern "C" fn(*mut AIBinder, u32, *mut *mut AParcel, *mut *mut AParcel, u32) -> BinderStatus),
306            dec_strong: dlsym_fn!(handle, "AIBinder_decStrong",
307                unsafe extern "C" fn(*mut AIBinder)),
308            parcel_delete: dlsym_fn!(handle, "AParcel_delete",
309                unsafe extern "C" fn(*mut AParcel)),
310            read_int32: dlsym_fn!(handle, "AParcel_readInt32",
311                unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus),
312            read_string: dlsym_fn!(handle, "AParcel_readString",
313                unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus),
314            write_strong_binder: dlsym_fn!(handle, "AParcel_writeStrongBinder",
315                unsafe extern "C" fn(*mut AParcel, *mut AIBinder) -> BinderStatus),
316            set_thread_pool_max: dlsym_fn!(handle, "ABinderProcess_setThreadPoolMaxThreadCount",
317                unsafe extern "C" fn(u32)),
318            join_thread_pool: dlsym_fn!(handle, "ABinderProcess_joinThreadPool",
319                unsafe extern "C" fn()),
320            get_user_data: dlsym_fn!(handle, "AIBinder_getUserData",
321                unsafe extern "C" fn(*const AIBinder) -> *mut c_void),
322            write_int32: dlsym_fn!(handle, "AParcel_writeInt32",
323                unsafe extern "C" fn(*mut AParcel, i32) -> BinderStatus),
324            read_bool: dlsym_opt!(handle, "AParcel_readBool",
325                unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus),
326        })
327    }
328
329    // ── ParcelReader ──────────────────────────────────────────────────────────
330
331    struct ParcelReader<'a> { vt: &'a Vtable, parcel: &'a OwnedParcel }
332
333    impl<'a> ParcelReader<'a> {
334        fn read_i32(&self) -> Result<i32, CoreError> {
335            let mut v = 0i32;
336            let s = unsafe { (self.vt.read_int32)(self.parcel.ptr, &mut v) };
337            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readInt32")); }
338            Ok(v)
339        }
340        fn read_string(&self) -> Result<Option<String>, CoreError> {
341            let mut buf = StringBuf::new();
342            let s = unsafe {
343                (self.vt.read_string)(self.parcel.ptr, &mut buf as *mut StringBuf as *mut c_void, string_alloc)
344            };
345            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readString")); }
346            Ok(buf.finish())
347        }
348        fn skip_i32s(&self, n: usize) -> Result<(), CoreError> {
349            for _ in 0..n { self.read_i32()?; }
350            Ok(())
351        }
352        fn skip_int_array(&self) -> Result<(), CoreError> {
353            let count = self.read_i32()?.max(0) as usize;
354            self.skip_i32s(count)
355        }
356        fn read_first_package_from_names(&self) -> Result<Option<String>, CoreError> {
357            let count = self.read_i32()?.max(0) as usize;
358            let mut first: Option<String> = None;
359            for _ in 0..count {
360                let s = self.read_string()?;
361                if first.is_none() {
362                    first = s.and_then(|c| c.split('/').next().map(str::to_owned));
363                }
364            }
365            Ok(first)
366        }
367    }
368
369    // ── Response parsers ──────────────────────────────────────────────────────
370
371    fn parse_stack_info_body(r: &ParcelReader<'_>) -> Result<Option<String>, CoreError> {
372        r.skip_i32s(5)?;
373        r.skip_int_array()?;
374        r.read_first_package_from_names()
375    }
376
377    // RootTaskInfo → (taskId, first childTaskName package). Walks the parcel
378    // once: prefix (bounds, childTaskIds), captures the first package from
379    // childTaskNames, then skips childTaskBounds / childTaskUserIds / visible /
380    // position / TaskInfo.userId to reach taskId — never touching the
381    // Intent/TaskInfo tail. taskId and pkg come from the same transaction, so
382    // callers pair them without a second (racy) round-trip.
383    fn parse_root_task_info_task(r: &ParcelReader<'_>) -> Result<(i32, Option<String>), CoreError> {
384        let scratch = r.read_i32()?;
385        if scratch != 0 { r.skip_i32s(4)?; }
386        r.skip_int_array()?; // childTaskIds
387        let pkg = r.read_first_package_from_names()?; // childTaskNames → pkg
388        // childTaskBounds: typed Rect array (nullable) — read count, skip 4 per entry
389        let bounds_count = r.read_i32()?;
390        let n = if bounds_count < 0 { 0 } else { bounds_count as usize };
391        for _ in 0..n {
392            let entry = r.read_i32()?;
393            if entry != 0 { r.skip_i32s(4)?; }
394        }
395        r.skip_int_array()?; // childTaskUserIds
396        r.skip_i32s(2)?;     // visible, position
397        r.skip_i32s(1)?;     // TaskInfo.userId
398        let task_id = r.read_i32()?;
399        Ok((task_id, pkg))
400    }
401
402    // ── Tx code resolution ────────────────────────────────────────────────────
403
404    pub struct TxCodes {
405        pub observer_code: u32,
406        pub query_code:    u32,
407        pub api_mode:      u8,  // 1 = RootTaskInfo, 2 = StackInfo
408        pub fg_code:       u32,
409    }
410
411    pub fn resolve_tx_codes() -> Result<TxCodes, CoreError> {
412        let (obs, query, api, fg) = dex::resolve_tx_codes_from_dex()
413            .ok_or_else(|| CoreError::binder(-1, "tx_code_resolution:dex_parse_failed"))?;
414        Ok(TxCodes { observer_code: obs, query_code: query, api_mode: api, fg_code: fg })
415    }
416
417    // ── ActivityManagerBinder ─────────────────────────────────────────────────
418
419    pub struct ActivityManagerBinder {
420        _lib:    DlHandle,
421        vt:      Vtable,
422        _class:  *mut AIBinder_Class,
423        service: OwnedBinder,
424        tx_code: u32,
425        legacy:  bool,
426    }
427    unsafe impl Send for ActivityManagerBinder {}
428
429    impl ActivityManagerBinder {
430        fn open_inner(handle: *mut c_void) -> Result<(DlHandle, Vtable, *mut AIBinder_Class, OwnedBinder), CoreError> {
431            let lib = DlHandle(handle);
432            let vt = load_vtable(handle)?;
433
434            let am_class = unsafe {
435                (vt.class_define)(
436                    AM_DESCRIPTOR.as_ptr() as *const c_char,
437                    am_on_create, am_on_destroy, am_on_transact,
438                )
439            };
440            if am_class.is_null() { return Err(CoreError::binder(-1, "AIBinder_Class_define:AM")); }
441
442            let raw = unsafe { (vt.get_service)(ACTIVITY_SERVICE.as_ptr() as *const c_char) };
443            if raw.is_null() { return Err(CoreError::binder(-1, "AServiceManager_getService:activity")); }
444            unsafe { (vt.associate_class)(raw, am_class) };
445
446            let service = OwnedBinder { ptr: raw, dec_strong: vt.dec_strong };
447            Ok((lib, vt, am_class, service))
448        }
449
450        fn dlopen_libbinder() -> Result<*mut c_void, CoreError> {
451            use std::os::raw::c_char;
452            let handle = unsafe {
453                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
454            };
455            if handle.is_null() { return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so")); }
456            Ok(handle)
457        }
458
459        /// Open ActivityManager binder (polling mode — no observer).
460        /// Resolves the query tx code from cache or DEX.
461        pub fn open() -> Result<Self, CoreError> {
462            let handle = Self::dlopen_libbinder()?;
463            let (lib, vt, class, service) = Self::open_inner(handle)?;
464            let codes = resolve_tx_codes()?;
465            let legacy = codes.api_mode == 2;
466            Ok(Self { _lib: lib, vt, _class: class, service, tx_code: codes.query_code, legacy })
467        }
468
469        /// Open ActivityManager binder and register as IProcessObserver.
470        ///
471        /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
472        /// callback fd. It becomes readable whenever `onForegroundActivitiesChanged`
473        /// fires. Caller must add it to epoll and may close it at any time — the
474        /// callback keeps writing to the core's copy, so closing the returned
475        /// fd never invalidates the notification path (C2). After the event
476        /// fires, call `get_focused_package`.
477        pub fn open_with_observer() -> Result<(Self, OwnedFd), CoreError> {
478            let handle = Self::dlopen_libbinder()?;
479            let (lib, vt, am_class, service) = Self::open_inner(handle)?;
480            let codes = resolve_tx_codes()?;
481            let legacy = codes.api_mode == 2;
482
483            // Create eventfd for callback → epoll bridge. Ownership stays in the
484            // core for the observer lifetime; the consumer receives a dup below.
485            let owned = unsafe {
486                let raw = libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC);
487                if raw < 0 { return Err(CoreError::sys(*libc::__errno(), "eventfd")); }
488                OwnedFd::from_raw_fd(raw)
489            };
490
491            // Define IProcessObserver class (we're the server)
492            let obs_class = unsafe {
493                (vt.class_define)(
494                    OBS_DESCRIPTOR.as_ptr() as *const c_char,
495                    obs_on_create, obs_on_destroy, obs_on_transact,
496                )
497            };
498            if obs_class.is_null() {
499                return Err(CoreError::binder(-1, "AIBinder_Class_define:Observer"));
500            }
501
502            // Instantiate our observer binder object
503            let obs_binder = unsafe { (vt.new_binder)(obs_class, std::ptr::null_mut()) };
504            if obs_binder.is_null() {
505                return Err(CoreError::binder(-1, "AIBinder_new:Observer"));
506            }
507            unsafe { (vt.associate_class)(obs_binder, obs_class) };
508
509            // Call registerProcessObserver(observer)
510            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
511            let s = unsafe { (vt.prepare_transaction)(service.ptr, &mut in_ptr) };
512            if s != STATUS_OK {
513                return Err(CoreError::binder(s, "prepareTransaction:registerObserver"));
514            }
515            unsafe { (vt.write_strong_binder)(in_ptr, obs_binder) };
516            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
517            let s = unsafe {
518                (vt.transact)(service.ptr, codes.observer_code, &mut in_ptr, &mut out_ptr, 0)
519            };
520            if !out_ptr.is_null() { unsafe { (vt.parcel_delete)(out_ptr) }; }
521            if s != STATUS_OK {
522                return Err(CoreError::binder(s, "transact:registerProcessObserver"));
523            }
524
525            // Consumer dup — made before publishing, so an error path drops the
526            // owned fd without ever leaving a stale handle for the callback.
527            let consumer = owned.try_clone()
528                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:observer"))?;
529
530            // Publish fg_code and the core-owned eventfd for the callback
531            OBS_FG_CODE.store(codes.fg_code, Ordering::Relaxed);
532            *obs_eventfd_guard() = Some(owned);
533
534            // Start binder thread pool — blocks forever in background thread
535            unsafe { (vt.set_thread_pool_max)(0) };
536            let join_fn = vt.join_thread_pool;
537            std::thread::spawn(move || unsafe { join_fn() });
538
539            let binder = Self { _lib: lib, vt, _class: am_class, service, tx_code: codes.query_code, legacy };
540            Ok((binder, consumer))
541        }
542
543        /// Open ActivityManager binder and register as the foreground process
544        /// observer.
545        ///
546        /// The authoritative foreground PID is delivered in the callback; this
547        /// is the low-noise foreground source. Two ROM variants are supported
548        /// and selected automatically:
549        ///
550        /// - Stock: `IForegroundProcessObserver.onForegroundProcessChanged`
551        ///   delivers a single `int pid`.
552        /// - Custom ROMs that dropped that interface instead deliver `(int pid,
553        ///   int uid, int fg)` through the repurposed
554        ///   `IProcessObserver.onForegroundActivitiesChanged`; this registers
555        ///   via `registerProcessObserver` and only signals on `fg != 0`.
556        ///
557        /// The callback stores the PID (readable via [`last_foreground_pid`])
558        /// and signals the returned eventfd.
559        ///
560        /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
561        /// callback fd. It becomes readable whenever a foreground process
562        /// change fires. Same lifetime contract as
563        /// [`ActivityManagerBinder::open_with_observer`] (C2): the core owns
564        /// the eventfd and the callback only ever writes to that copy, so
565        /// closing the returned dup never invalidates the notification path.
566        pub fn open_with_fgproc_observer() -> Result<(Self, OwnedFd), CoreError> {
567            let handle = Self::dlopen_libbinder()?;
568            let (lib, vt, am_class, service) = Self::open_inner(handle)?;
569
570            // Resolve the foreground-observer tx codes. Prefer the stock
571            // IForegroundProcessObserver path; fall back to the custom
572            // IProcessObserver pid-carrying form on ROMs that dropped it.
573            // mode 0 = stock single-int callback, mode 1 = (pid, uid, fg).
574            let (register_code, fgproc_code, mode, descriptor): (u32, u32, u32, &[u8]) =
575                match crate::dex::resolve_fgproc_codes() {
576                    Some((r, c)) => (r, c, 0, FGPROC_DESCRIPTOR),
577                    None => match crate::dex::resolve_fgproc_codes_fallback() {
578                        Some((r, c)) => (r, c, 1, OBS_DESCRIPTOR),
579                        None => {
580                            return Err(CoreError::binder(-1, "tx_code_resolution:fgproc_dex_parse_failed"));
581                        }
582                    },
583                };
584
585            // Create eventfd for callback → epoll bridge. Ownership stays in the
586            // core for the observer lifetime; the consumer receives a dup below.
587            let owned = unsafe {
588                let raw = libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC);
589                if raw < 0 { return Err(CoreError::sys(*libc::__errno(), "eventfd")); }
590                OwnedFd::from_raw_fd(raw)
591            };
592
593            // Define our observer class (we're the server). The descriptor must
594            // match whichever interface we actually register as.
595            let obs_class = unsafe {
596                (vt.class_define)(
597                    descriptor.as_ptr() as *const c_char,
598                    fgproc_on_create, fgproc_on_destroy, fgproc_on_transact,
599                )
600            };
601            if obs_class.is_null() {
602                return Err(CoreError::binder(-1, "AIBinder_Class_define:FGProcessObserver"));
603            }
604
605            // Instantiate our observer binder object
606            let obs_binder = unsafe { (vt.new_binder)(obs_class, std::ptr::null_mut()) };
607            if obs_binder.is_null() {
608                return Err(CoreError::binder(-1, "AIBinder_new:FGProcessObserver"));
609            }
610            unsafe { (vt.associate_class)(obs_binder, obs_class) };
611
612            // Call registerForegroundProcessObserver(observer) or the fallback
613            // registerProcessObserver(observer) depending on resolved mode.
614            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
615            let s = unsafe { (vt.prepare_transaction)(service.ptr, &mut in_ptr) };
616            if s != STATUS_OK {
617                return Err(CoreError::binder(s, "prepareTransaction:registerForegroundProcessObserver"));
618            }
619            unsafe { (vt.write_strong_binder)(in_ptr, obs_binder) };
620            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
621            let s = unsafe {
622                (vt.transact)(service.ptr, register_code, &mut in_ptr, &mut out_ptr, 0)
623            };
624            if !out_ptr.is_null() { unsafe { (vt.parcel_delete)(out_ptr) }; }
625            if s != STATUS_OK {
626                return Err(CoreError::binder(s, "transact:registerForegroundProcessObserver"));
627            }
628
629            // Consumer dup — made before publishing, so an error path drops the
630            // owned fd without ever leaving a stale handle for the callback.
631            let consumer = owned.try_clone()
632                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:fgproc_observer"))?;
633
634            // Publish reader fn, mode, fg code, pid base, and the core-owned
635            // eventfd for the callback. Reader and mode are published first so
636            // the callback never sees a matching code with an unset reader or
637            // mode (C2-adjacent init order).
638            FGPROC_READ_I32.store(vt.read_int32 as usize, Ordering::Relaxed);
639            FGPROC_IPROC_MODE.store(mode, Ordering::Relaxed);
640            FGPROC_FG_CODE.store(fgproc_code, Ordering::Relaxed);
641            FGPROC_PID.store(0, Ordering::Relaxed);
642            *fgproc_eventfd_guard() = Some(owned);
643
644            // Start binder thread pool — blocks forever in background thread
645            unsafe { (vt.set_thread_pool_max)(0) };
646            let join_fn = vt.join_thread_pool;
647            std::thread::spawn(move || unsafe { join_fn() });
648
649            let binder = Self { _lib: lib, vt, _class: am_class, service, tx_code: 0, legacy: false };
650            Ok((binder, consumer))
651        }
652
653        fn do_transact(&self) -> Result<OwnedParcel, CoreError> {
654            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
655            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut in_ptr) };
656            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
657            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
658            let s = unsafe {
659                (self.vt.transact)(self.service.ptr, self.tx_code, &mut in_ptr, &mut out_ptr, 0)
660            };
661            let out = OwnedParcel { ptr: out_ptr, delete: self.vt.parcel_delete };
662            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
663            Ok(out)
664        }
665
666        /// The focused root task's `(taskId, topActivity package)` from a single
667        /// txn-31 transaction. Outer `None` = no focused root task (or legacy
668        /// API 29, where the reply is `StackInfo` and carries no taskId); inner
669        /// `None` = task known but no package in `childTaskNames`. Both values
670        /// come from the same parcel, so the registration key and the report
671        /// tag can never diverge.
672        pub fn get_focused_task(&self) -> Result<Option<(i32, Option<String>)>, CoreError> {
673            if self.legacy {
674                // StackInfo has no taskId — report None rather than a wrong id.
675                return Ok(None);
676            }
677            let out = self.do_transact()?;
678            let r = ParcelReader { vt: &self.vt, parcel: &out };
679            let ex = r.read_i32()?;
680            if ex != EX_NONE { return Err(CoreError::binder(ex, "getFocusedTask:exception")); }
681            let present = r.read_i32()?;
682            if present == 0 { return Ok(None); }
683            Ok(Some(parse_root_task_info_task(&r)?))
684        }
685
686        /// The `topActivity` package of the focused root task (legacy API 29
687        /// builds use `StackInfo` and still resolve the package). Thin wrapper
688        /// over [`ActivityManagerBinder::get_focused_task`].
689        pub fn get_focused_package(&self) -> Result<Option<String>, CoreError> {
690            if self.legacy {
691                let out = self.do_transact()?;
692                let r = ParcelReader { vt: &self.vt, parcel: &out };
693                let ex = r.read_i32()?;
694                if ex != EX_NONE { return Err(CoreError::binder(ex, "getFocusedTask:exception")); }
695                let present = r.read_i32()?;
696                if present == 0 { return Ok(None); }
697                return parse_stack_info_body(&r);
698            }
699            Ok(self.get_focused_task()?.map(|(_, pkg)| pkg).flatten())
700        }
701
702        /// The `taskId` of the currently focused root task. Thin wrapper over
703        /// [`ActivityManagerBinder::get_focused_task`]; returns `None` when there
704        /// is no focused root task or on legacy API 29 builds.
705        pub fn get_focused_task_id(&self) -> Result<Option<i32>, CoreError> {
706            Ok(self.get_focused_task()?.map(|(task_id, _)| task_id))
707        }
708    }
709
710    // ── DisplayManagerBinder ─────────────────────────────────────────────────
711
712    const DISPLAY_SERVICE:    &[u8] = b"display\0";
713    const DISPLAY_DESCRIPTOR: &[u8] = b"android.hardware.display.IDisplayManager\0";
714    const CALLBACK_DESCRIPTOR: &[u8] = b"android.hardware.display.IDisplayManagerCallback\0";
715    const POWER_SERVICE:      &[u8] = b"power\0";
716
717    const TX_DISPLAY_REGISTER_CALLBACK: u32 = 4;
718
719    // Core owns the callback eventfd; the consumer gets a dup and may close it
720    // freely. Same lifetime discipline as the ActivityManager observer (C2).
721    static DISP_EVENTFD: Mutex<Option<OwnedFd>> = Mutex::new(None);
722
723    fn disp_eventfd_guard() -> std::sync::MutexGuard<'static, Option<OwnedFd>> {
724        DISP_EVENTFD.lock().unwrap_or_else(|p| p.into_inner())
725    }
726
727    unsafe extern "C" fn disp_cb_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
728    unsafe extern "C" fn disp_cb_on_destroy(_: *mut c_void) {}
729    unsafe extern "C" fn disp_cb_on_transact(
730        _: *mut AIBinder, code: u32, _: *const AParcel, _: *mut AParcel,
731    ) -> BinderStatus {
732        if code == 1 {
733            if let Some(fd) = disp_eventfd_guard().as_ref() {
734                let val: u64 = 1;
735                unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
736            }
737        }
738        STATUS_OK
739    }
740
741    pub struct DisplayManagerBinder {
742        _lib:           DlHandle,
743        vt:             Vtable,
744        display:        OwnedBinder,
745        power:          Option<OwnedBinder>,
746        is_interactive_tx: u32,
747    }
748    unsafe impl Send for DisplayManagerBinder {}
749
750    impl DisplayManagerBinder {
751        pub fn open_with_callback() -> Result<(Self, crate::reactor::Fd), CoreError> {
752            let handle = unsafe {
753                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
754            };
755            if handle.is_null() { return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so")); }
756            let lib = DlHandle(handle);
757            let vt = load_vtable(handle)?;
758
759            // Blocking eventfd (no EFD_NONBLOCK) — callback writes, caller's
760            // read_u64_blocking() waits. The core owns it for the callback's
761            // lifetime; the consumer receives a dup below (C2).
762            let owned = unsafe {
763                let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
764                if raw < 0 { return Err(CoreError::sys(*libc::__errno(), "eventfd")); }
765                OwnedFd::from_raw_fd(raw)
766            };
767
768            // Get display service (no class_define needed for client-only)
769            let raw_display = unsafe { (vt.get_service)(DISPLAY_SERVICE.as_ptr() as *const c_char) };
770            if raw_display.is_null() {
771                return Err(CoreError::binder(-1, "AServiceManager_getService:display"));
772            }
773            let display = OwnedBinder { ptr: raw_display, dec_strong: vt.dec_strong };
774
775            // Define IDisplayManagerCallback (we're the server receiving callbacks)
776            let cb_class = unsafe {
777                (vt.class_define)(
778                    CALLBACK_DESCRIPTOR.as_ptr() as *const c_char,
779                    disp_cb_on_create, disp_cb_on_destroy, disp_cb_on_transact,
780                )
781            };
782            if cb_class.is_null() {
783                return Err(CoreError::binder(-1, "AIBinder_Class_define:DisplayCallback"));
784            }
785
786            let cb_binder = unsafe { (vt.new_binder)(cb_class, std::ptr::null_mut()) };
787            if cb_binder.is_null() {
788                return Err(CoreError::binder(-1, "AIBinder_new:DisplayCallback"));
789            }
790
791            // registerCallback(callback) — tx 4
792            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
793            let s = unsafe { (vt.prepare_transaction)(display.ptr, &mut in_ptr) };
794            if s != STATUS_OK {
795                return Err(CoreError::binder(s, "prepareTransaction:registerCallback"));
796            }
797            unsafe { (vt.write_strong_binder)(in_ptr, cb_binder) };
798            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
799            let s = unsafe {
800                (vt.transact)(display.ptr, TX_DISPLAY_REGISTER_CALLBACK, &mut in_ptr, &mut out_ptr, 0)
801            };
802            if !out_ptr.is_null() { unsafe { (vt.parcel_delete)(out_ptr) }; }
803            if s != STATUS_OK {
804                return Err(CoreError::binder(s, "transact:registerCallback"));
805            }
806
807            // Optional: grab power service for is_interactive()
808            let power = {
809                let raw = unsafe { (vt.get_service)(POWER_SERVICE.as_ptr() as *const c_char) };
810                if raw.is_null() { None } else { Some(OwnedBinder { ptr: raw, dec_strong: vt.dec_strong }) }
811            };
812
813            // Resolve isInteractive tx code from DEX at open time
814            let is_interactive_tx = crate::dex::resolve_is_interactive_tx()
815                .ok_or_else(|| CoreError::binder(-1, "dex:TRANSACTION_isInteractive not found"))?;
816
817            // Consumer dup — made before publishing, so an error path drops the
818            // owned fd without ever leaving a stale handle for the callback.
819            let efd_owned = owned.try_clone()
820                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:display"))
821                .and_then(|dup| unsafe {
822                    crate::reactor::Fd::from_owned_raw_fd(dup.into_raw_fd(), "display.efd")
823                        .map_err(|_| CoreError::binder(-1, "Fd::from_owned_raw_fd:display.efd"))
824                })?;
825
826            // Publish the core-owned eventfd for the callback
827            *disp_eventfd_guard() = Some(owned);
828
829            // Join binder thread pool so callbacks can fire
830            unsafe { (vt.set_thread_pool_max)(0) };
831            let join_fn = vt.join_thread_pool;
832            std::thread::spawn(move || unsafe { join_fn() });
833
834            Ok((Self { _lib: lib, vt, display, power, is_interactive_tx }, efd_owned))
835        }
836
837        pub fn is_interactive(&self) -> Result<bool, CoreError> {
838            let power = self.power.as_ref()
839                .ok_or_else(|| CoreError::binder(-1, "power:unavailable"))?;
840            let mut inp: *mut AParcel = std::ptr::null_mut();
841            let s = unsafe { (self.vt.prepare_transaction)(power.ptr, &mut inp) };
842            if s != STATUS_OK { return Err(CoreError::binder(s, "prepareTransaction:isInteractive")); }
843            let mut out: *mut AParcel = std::ptr::null_mut();
844            let s = unsafe {
845                (self.vt.transact)(power.ptr, self.is_interactive_tx, &mut inp, &mut out, 0)
846            };
847            let out = OwnedParcel { ptr: out, delete: self.vt.parcel_delete };
848            if s != STATUS_OK { return Err(CoreError::binder(s, "transact:isInteractive")); }
849            let r = ParcelReader { vt: &self.vt, parcel: &out };
850            let ex = r.read_i32()?;
851            if ex != EX_NONE { return Err(CoreError::binder(ex, "isInteractive:exception")); }
852            if let Some(rb) = self.vt.read_bool {
853                let mut v = false;
854                let s = unsafe { rb(out.ptr as *const AParcel, &mut v) };
855                if s != STATUS_OK { return Err(CoreError::binder(s, "readBool:isInteractive")); }
856                Ok(v)
857            } else {
858                Ok(r.read_i32()? != 0)
859            }
860        }
861    }
862
863    // ── FpsListener (task FPS callback) ───────────────────────────────────────
864
865    const WINDOW_SERVICE:    &[u8] = b"window\0";
866    const WM_DESCRIPTOR:     &[u8] = b"android.view.IWindowManager\0";
867    const FPS_DESCRIPTOR:    &[u8] = b"android.window.ITaskFpsCallback\0";
868    // The last reported FPS (bit pattern of the f32) is published before the
869    // eventfd is signalled, so the consumer never reads a stale value. The wake
870    // eventfd is per-instance (same pattern as TaskStackListener): it is handed
871    // to AIBinder_new as the callback binder's userdata, so a second
872    // FpsListener can never rewire an earlier registration's wake into its own
873    // fd (the callback resolves its own binder's fd via AIBinder_getUserData).
874    static FPS_VALUE: AtomicU32 = AtomicU32::new(0);
875    static FPS_CODE: AtomicU32 = AtomicU32::new(0);
876    static FPS_READ_I32: AtomicUsize = AtomicUsize::new(0);
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    // The per-instance wake eventfd is this callback binder's userdata, so
888    // onCreate must return the args passed to AIBinder_new — AIBinder_getUserData
889    // returns exactly that value — and onDestroy must reclaim the box (same
890    // pattern as TaskStackListener). Returning null here would make
891    // AIBinder_getUserData return null, silently breaking the FPS wake.
892    unsafe extern "C" fn fps_on_create(userdata: *mut c_void) -> *mut c_void { userdata }
893    unsafe extern "C" fn fps_on_destroy(userdata: *mut c_void) {
894        if !userdata.is_null() {
895            unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
896        }
897    }
898    unsafe extern "C" fn fps_on_transact(
899        binder: *mut AIBinder, code: u32, in_parcel: *const AParcel, _: *mut AParcel,
900    ) -> BinderStatus {
901        if code != FPS_CODE.load(Ordering::Relaxed) {
902            return STATUS_UNKNOWN_TRANSACTION;
903        }
904        // Reader is published non-zero before the code, so a matching code is
905        // never paired with an unset reader.
906        let read_addr = FPS_READ_I32.load(Ordering::Relaxed);
907        if read_addr != 0 {
908            let read_fn: unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus =
909                unsafe { std::mem::transmute(read_addr) };
910            let mut bits: i32 = 0;
911            if unsafe { read_fn(in_parcel, &mut bits) } == STATUS_OK {
912                // Publish the value before signalling so the consumer always
913                // sees the value that triggered the wakeup.
914                FPS_VALUE.store(bits as u32, Ordering::Relaxed);
915                // Per-instance wake: the eventfd is this callback binder's
916                // userdata (see FpsListener::open), so two FpsListeners never
917                // cross-wire their wakes. A missing slot means the process-wide
918                // AIBinder_getUserData symbol has not been cached — drop the
919                // signal rather than risk a stale fd.
920                let get_user_data = GET_USER_DATA
921                    .lock()
922                    .unwrap_or_else(|p| p.into_inner());
923                if let Some(get_user_data) = *get_user_data {
924                    let userdata = unsafe { get_user_data(binder) };
925                    if !userdata.is_null() {
926                        let efd = userdata as *mut OwnedFd;
927                        let val: u64 = 1;
928                        unsafe {
929                            libc::write((*efd).as_raw_fd(), &val as *const u64 as *const c_void, 8)
930                        };
931                    }
932                }
933            }
934        }
935        STATUS_OK
936    }
937
938    /// Push-based per-task FPS listener registered with `WindowManager`.
939    ///
940    /// Uses `IWindowManager.registerTaskFpsCallback(taskId, callback)`; the
941    /// daemon hosts the `ITaskFpsCallback` server object and receives
942    /// `onFpsReported(float)` one-way transactions from the `FpsReporter` at
943    /// most every ~500 ms.
944    ///
945    /// The registering UID must hold `ACCESS_FPS_COUNTER` (signature|privileged)
946    /// — this process typically runs as shell (uid 2000) via `su`.
947    ///
948    /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
949    /// callback fd. It becomes readable whenever `onFpsReported` fires; call
950    /// [`FpsListener::last_fps`] after the event to read the value.
951    pub struct FpsListener {
952        _lib:       DlHandle,
953        vt:         Vtable,
954        window:     OwnedBinder,
955        cb_binder:  *mut AIBinder,
956        _wm_class:  *mut AIBinder_Class,
957        register_code: u32,
958        unregister_code: u32,
959        task_id:    i32,
960    }
961    unsafe impl Send for FpsListener {}
962
963    impl FpsListener {
964        /// Open WindowManager and define the `ITaskFpsCallback` server object.
965        ///
966        /// Resolves the three tx codes from DEX. Does **not** register a task
967        /// yet — call [`FpsListener::register`] once a taskId is known. Starts
968        /// the binder thread pool so `onFpsReported` can fire.
969        pub fn open() -> Result<(Self, OwnedFd), CoreError> {
970            let handle = unsafe {
971                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
972            };
973            if handle.is_null() { return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so")); }
974            let lib = DlHandle(handle);
975            let vt = load_vtable(handle)?;
976
977            let (register_code, unregister_code, on_fps_code) =
978                crate::dex::resolve_fps_codes()
979                    .ok_or_else(|| CoreError::binder(-1, "dex:TRANSACTION_registerTaskFpsCallback not found"))?;
980
981            let window = {
982                let raw = unsafe { (vt.get_service)(WINDOW_SERVICE.as_ptr() as *const c_char) };
983                if raw.is_null() { return Err(CoreError::binder(-1, "AServiceManager_getService:window")); }
984                OwnedBinder { ptr: raw, dec_strong: vt.dec_strong }
985            };
986
987            // Remote transactions require a class on the binder (same
988            // AIBinder_prepareTransaction contract as the AM service above).
989            let wm_class = unsafe {
990                (vt.class_define)(
991                    WM_DESCRIPTOR.as_ptr() as *const c_char,
992                    wm_on_create, wm_on_destroy, wm_on_transact,
993                )
994            };
995            if wm_class.is_null() {
996                return Err(CoreError::binder(-1, "AIBinder_Class_define:IWindowManager"));
997            }
998            unsafe { (vt.associate_class)(window.ptr, wm_class) };
999
1000            let cb_class = unsafe {
1001                (vt.class_define)(
1002                    FPS_DESCRIPTOR.as_ptr() as *const c_char,
1003                    fps_on_create, fps_on_destroy, fps_on_transact,
1004                )
1005            };
1006            if cb_class.is_null() {
1007                return Err(CoreError::binder(-1, "AIBinder_Class_define:ITaskFpsCallback"));
1008            }
1009
1010            // Blocking eventfd — callback writes, consumer waits/reads. Each
1011            // instance owns its own fd; it is handed to the callback binder as
1012            // userdata (per-instance routing, no process-wide static) and the
1013            // consumer receives a dup below (C2).
1014            let owned = unsafe {
1015                let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
1016                if raw < 0 { return Err(CoreError::sys(*libc::__errno(), "eventfd")); }
1017                OwnedFd::from_raw_fd(raw)
1018            };
1019
1020            let consumer = owned.try_clone()
1021                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:fps"))?;
1022
1023            let userdata = Box::into_raw(Box::new(owned)) as *mut c_void;
1024            let cb_binder = unsafe { (vt.new_binder)(cb_class, userdata) };
1025            if cb_binder.is_null() {
1026                // Reclaim the userdata box handed to AIBinder_new before bailing.
1027                unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
1028                return Err(CoreError::binder(-1, "AIBinder_new:ITaskFpsCallback"));
1029            }
1030            unsafe { (vt.associate_class)(cb_binder, cb_class) };
1031
1032            // Publish the reader and code before the eventfd registration; a
1033            // matching code is never paired with an unset reader (C2-adjacent).
1034            FPS_READ_I32.store(vt.read_int32 as usize, Ordering::Relaxed);
1035            FPS_CODE.store(on_fps_code, Ordering::Relaxed);
1036            FPS_VALUE.store(0, Ordering::Relaxed);
1037            // The callback resolves AIBinder_getUserData from this vtable; the
1038            // symbol address is process-wide, so a cached static is safe.
1039            *GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner()) = Some(vt.get_user_data);
1040
1041            unsafe { (vt.set_thread_pool_max)(0) };
1042            let join_fn = vt.join_thread_pool;
1043            std::thread::spawn(move || unsafe { join_fn() });
1044
1045            Ok((Self {
1046                _lib: lib, vt, window, cb_binder, _wm_class: wm_class,
1047                register_code, unregister_code, task_id: -1,
1048            }, consumer))
1049        }
1050
1051        /// Register the callback for `task_id`. If a task was already
1052        /// registered, it is unregistered first (WindowManager tracks one task
1053        /// per callback binder).
1054        pub fn register(&mut self, task_id: i32) -> Result<(), CoreError> {
1055            if self.task_id == task_id {
1056                return Ok(());
1057            }
1058            if self.task_id >= 0 {
1059                let _ = self.unregister();
1060            }
1061
1062            let mut inp: *mut AParcel = std::ptr::null_mut();
1063            let s = unsafe { (self.vt.prepare_transaction)(self.window.ptr, &mut inp) };
1064            if s != STATUS_OK { return Err(CoreError::binder(s, "prepareTransaction:registerTaskFpsCallback")); }
1065            let s = unsafe { (self.vt.write_int32)(inp, task_id) };
1066            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_writeInt32:taskId")); }
1067            let s = unsafe { (self.vt.write_strong_binder)(inp, self.cb_binder) };
1068            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_writeStrongBinder")); }
1069            let mut out: *mut AParcel = std::ptr::null_mut();
1070            let s = unsafe {
1071                (self.vt.transact)(self.window.ptr, self.register_code, &mut inp, &mut out, 0)
1072            };
1073            if !out.is_null() { unsafe { (self.vt.parcel_delete)(out) }; }
1074            if s != STATUS_OK {
1075                return Err(CoreError::binder(s, "transact:registerTaskFpsCallback"));
1076            }
1077            self.task_id = task_id;
1078            Ok(())
1079        }
1080
1081        /// Unregister the callback from WindowManager. No-op if nothing is
1082        /// registered.
1083        pub fn unregister(&mut self) -> Result<(), CoreError> {
1084            if self.task_id < 0 {
1085                return Ok(());
1086            }
1087            let mut inp: *mut AParcel = std::ptr::null_mut();
1088            let s = unsafe { (self.vt.prepare_transaction)(self.window.ptr, &mut inp) };
1089            if s != STATUS_OK { return Err(CoreError::binder(s, "prepareTransaction:unregisterTaskFpsCallback")); }
1090            let s = unsafe { (self.vt.write_strong_binder)(inp, self.cb_binder) };
1091            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_writeStrongBinder")); }
1092            let mut out: *mut AParcel = std::ptr::null_mut();
1093            let s = unsafe {
1094                (self.vt.transact)(self.window.ptr, self.unregister_code, &mut inp, &mut out, 0)
1095            };
1096            if !out.is_null() { unsafe { (self.vt.parcel_delete)(out) }; }
1097            if s != STATUS_OK {
1098                return Err(CoreError::binder(s, "transact:unregisterTaskFpsCallback"));
1099            }
1100            self.task_id = -1;
1101            Ok(())
1102        }
1103
1104        /// The most recent `onFpsReported` value (f32), or `None` if no report
1105        /// has arrived yet. Safe to call at any time; the bit pattern is
1106        /// published atomically.
1107        pub fn last_fps(&self) -> Option<f32> {
1108            let bits = FPS_VALUE.load(Ordering::Relaxed);
1109            if bits == 0 { None } else { Some(f32::from_bits(bits)) }
1110        }
1111
1112        /// The taskId currently registered, or `None` if none.
1113        pub fn task_id(&self) -> Option<i32> {
1114            (self.task_id >= 0).then_some(self.task_id)
1115        }
1116    }
1117
1118    impl Drop for FpsListener {
1119        /// Best-effort deregistration from WindowManager so a dropped listener
1120        /// does not leave the framework delivering `onFpsReported` forever. The
1121        /// callback binder's local strong ref is intentionally NOT released:
1122        /// keeping it alive guarantees the per-binder userdata (the OwnedFd)
1123        /// can never be reclaimed by `on_destroy` while a callback is in
1124        /// flight, and the framework-side registration has been dropped by the
1125        /// unregister, so no stale transaction targets this instance.
1126        fn drop(&mut self) {
1127            let _ = self.unregister();
1128        }
1129    }
1130
1131    // ── TaskStackListener (task-stack change wake-up) ────────────────────────
1132
1133    const TASK_SERVICE:     &[u8] = b"activity_task\0";
1134    const ATM_DESCRIPTOR:   &[u8] = b"android.app.IActivityTaskManager\0";
1135    const TASK_STACK_DESCRIPTOR: &[u8] = b"android.app.ITaskStackListener\0";
1136
1137    // No-op callbacks for the client-only IActivityTaskManager class (we never
1138    // serve transactions on the `activity_task` binder — the class exists only
1139    // to satisfy AIBinder_prepareTransaction's remote-transaction contract).
1140    unsafe extern "C" fn atm_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
1141    unsafe extern "C" fn atm_on_destroy(_: *mut c_void) {}
1142    unsafe extern "C" fn atm_on_transact(
1143        _: *mut AIBinder, _: u32, _: *const AParcel, _: *mut AParcel,
1144    ) -> BinderStatus { STATUS_OK }
1145
1146    // Pure wake-up handler: any ITaskStackListener callback
1147    // (onTaskStackChanged, onTaskMovedToFront, …) just signals the eventfd.
1148    // The callback arguments are deliberately NOT parsed — the authoritative
1149    // (taskId, pkg) comes from re-querying getFocusedRootTaskInfo (txn 31) on
1150    // the event, so we never depend on a parcel layout (RunningTaskInfo places
1151    // taskId near the parcel tail).
1152    //
1153    // The wake eventfd is per-instance: the daemon hosts two listeners (the fg
1154    // task source and the fps channel), each with its own eventfd. It is handed
1155    // to AIBinder_new as the binder's userdata, so on_destroy must reclaim it.
1156    // No process-wide static — a shared fd would deliver every instance's
1157    // wake to whichever listener opened last.
1158    //
1159    // The callback resolves the per-binder eventfd through AIBinder_getUserData.
1160    // The symbol address is process-wide, so it is cached once in a static.
1161    static GET_USER_DATA: std::sync::Mutex<Option<unsafe extern "C" fn(*const AIBinder) -> *mut c_void>> =
1162        std::sync::Mutex::new(None);
1163
1164    unsafe extern "C" fn task_stack_on_create(userdata: *mut c_void) -> *mut c_void { userdata }
1165    unsafe extern "C" fn task_stack_on_destroy(userdata: *mut c_void) {
1166        if !userdata.is_null() {
1167            unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
1168        }
1169    }
1170    unsafe extern "C" fn task_stack_on_transact(
1171        binder: *mut AIBinder, _code: u32, _in_parcel: *const AParcel, _reply: *mut AParcel,
1172    ) -> BinderStatus {
1173        let get_user_data = GET_USER_DATA
1174            .lock()
1175            .unwrap_or_else(|p| p.into_inner());
1176        if let Some(get_user_data) = *get_user_data {
1177            let userdata = unsafe { get_user_data(binder) };
1178            if !userdata.is_null() {
1179                let efd = userdata as *mut OwnedFd;
1180                let val: u64 = 1;
1181                unsafe { libc::write((*efd).as_raw_fd(), &val as *const u64 as *const c_void, 8) };
1182            }
1183        }
1184        STATUS_OK
1185    }
1186
1187    /// Push-based task-stack change listener registered with
1188    /// `IActivityTaskManager`.
1189    ///
1190    /// Uses `IActivityTaskManager.registerTaskStackListener(listener)`; the
1191    /// daemon hosts the `ITaskStackListener` server object. The callback is a
1192    /// pure wake-up: on any task-stack change it signals the eventfd and
1193    /// parses nothing. Consumers re-query `getFocusedRootTaskInfo` (txn 31) on
1194    /// the event for the authoritative `(taskId, pkg)`.
1195    ///
1196    /// This target's ROM exposes the legacy `ITaskStackListener` /
1197    /// `registerTaskStackListener` pair; the newer `ITaskChangeListener` /
1198    /// `registerTaskChangeListener` interface is absent.
1199    ///
1200    /// The registering UID must hold `MANAGE_ACTIVITY_TASKS` /
1201    /// `MANAGE_ACTIVITY_STACKS` — the same gate as txn 31, which root passes
1202    /// empirically on the target ROM.
1203    ///
1204    /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
1205    /// callback fd. It becomes readable on any task-stack change.
1206    pub struct TaskStackListener {
1207        _lib:       DlHandle,
1208        vt:         Vtable,
1209        service:    OwnedBinder,
1210        cb_binder:  *mut AIBinder,
1211        _atm_class: *mut AIBinder_Class,
1212        register_code: u32,
1213        unregister_code: u32,
1214    }
1215    unsafe impl Send for TaskStackListener {}
1216
1217    impl TaskStackListener {
1218        /// Open `activity_task`, define the `ITaskStackListener` server object,
1219        /// and start the binder thread pool. Does **not** register yet — call
1220        /// [`TaskStackListener::register`] once consumers are active.
1221        pub fn open() -> Result<(Self, OwnedFd), CoreError> {
1222            let handle = unsafe {
1223                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
1224            };
1225            if handle.is_null() { return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so")); }
1226            let lib = DlHandle(handle);
1227            let vt = load_vtable(handle)?;
1228
1229            let (register_code, unregister_code) =
1230                crate::dex::resolve_task_stack_codes()
1231                    .ok_or_else(|| CoreError::binder(-1, "dex:TRANSACTION_registerTaskStackListener not found"))?;
1232
1233            let service = {
1234                let raw = unsafe { (vt.get_service)(TASK_SERVICE.as_ptr() as *const c_char) };
1235                if raw.is_null() { return Err(CoreError::binder(-1, "AServiceManager_getService:activity_task")); }
1236                OwnedBinder { ptr: raw, dec_strong: vt.dec_strong }
1237            };
1238
1239            // Remote transactions require a class on the binder (same
1240            // AIBinder_prepareTransaction contract as the other services).
1241            let atm_class = unsafe {
1242                (vt.class_define)(
1243                    ATM_DESCRIPTOR.as_ptr() as *const c_char,
1244                    atm_on_create, atm_on_destroy, atm_on_transact,
1245                )
1246            };
1247            if atm_class.is_null() {
1248                return Err(CoreError::binder(-1, "AIBinder_Class_define:IActivityTaskManager"));
1249            }
1250            unsafe { (vt.associate_class)(service.ptr, atm_class) };
1251
1252            let cb_class = unsafe {
1253                (vt.class_define)(
1254                    TASK_STACK_DESCRIPTOR.as_ptr() as *const c_char,
1255                    task_stack_on_create, task_stack_on_destroy, task_stack_on_transact,
1256                )
1257            };
1258            if cb_class.is_null() {
1259                return Err(CoreError::binder(-1, "AIBinder_Class_define:ITaskStackListener"));
1260            }
1261
1262            // Blocking eventfd — callback writes, consumer waits/reads. Each
1263            // instance owns its own fd; it is handed to the callback binder as
1264            // userdata (per-instance routing, no process-wide static) and the
1265            // consumer receives a dup below (C2).
1266            let owned = unsafe {
1267                let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
1268                if raw < 0 { return Err(CoreError::sys(*libc::__errno(), "eventfd")); }
1269                OwnedFd::from_raw_fd(raw)
1270            };
1271
1272            let consumer = owned.try_clone()
1273                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:task_stack"))?;
1274
1275            let userdata = Box::into_raw(Box::new(owned)) as *mut c_void;
1276            let cb_binder = unsafe { (vt.new_binder)(cb_class, userdata) };
1277            if cb_binder.is_null() {
1278                // Reclaim the userdata box handed to AIBinder_new before bailing.
1279                unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
1280                return Err(CoreError::binder(-1, "AIBinder_new:ITaskStackListener"));
1281            }
1282            unsafe { (vt.associate_class)(cb_binder, cb_class) };
1283
1284            // The callback resolves AIBinder_getUserData from this vtable; the
1285            // symbol address is process-wide, so a cached static is safe.
1286            *GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner()) = Some(vt.get_user_data);
1287
1288            unsafe { (vt.set_thread_pool_max)(0) };
1289            let join_fn = vt.join_thread_pool;
1290            std::thread::spawn(move || unsafe { join_fn() });
1291
1292            Ok((Self {
1293                _lib: lib, vt, service, cb_binder, _atm_class: atm_class,
1294                register_code, unregister_code,
1295            }, consumer))
1296        }
1297
1298        /// Register the task-stack listener with `activity_task` (one listener
1299        /// receives all task-stack events). Idempotent at the framework level;
1300        /// callers should register once and keep the object alive.
1301        pub fn register(&self) -> Result<(), CoreError> {
1302            let mut inp: *mut AParcel = std::ptr::null_mut();
1303            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut inp) };
1304            if s != STATUS_OK { return Err(CoreError::binder(s, "prepareTransaction:registerTaskStackListener")); }
1305            let s = unsafe { (self.vt.write_strong_binder)(inp, self.cb_binder) };
1306            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_writeStrongBinder")); }
1307            let mut out: *mut AParcel = std::ptr::null_mut();
1308            let s = unsafe {
1309                (self.vt.transact)(self.service.ptr, self.register_code, &mut inp, &mut out, 0)
1310            };
1311            if !out.is_null() { unsafe { (self.vt.parcel_delete)(out) }; }
1312            if s != STATUS_OK {
1313                return Err(CoreError::binder(s, "transact:registerTaskStackListener"));
1314            }
1315            Ok(())
1316        }
1317
1318        /// Unregister the task-stack listener from `activity_task`. No-op at
1319        /// the framework level if not registered; callers should unregister
1320        /// before dropping the object.
1321        pub fn unregister(&self) -> Result<(), CoreError> {
1322            let mut inp: *mut AParcel = std::ptr::null_mut();
1323            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut inp) };
1324            if s != STATUS_OK { return Err(CoreError::binder(s, "prepareTransaction:unregisterTaskStackListener")); }
1325            let s = unsafe { (self.vt.write_strong_binder)(inp, self.cb_binder) };
1326            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_writeStrongBinder")); }
1327            let mut out: *mut AParcel = std::ptr::null_mut();
1328            let s = unsafe {
1329                (self.vt.transact)(self.service.ptr, self.unregister_code, &mut inp, &mut out, 0)
1330            };
1331            if !out.is_null() { unsafe { (self.vt.parcel_delete)(out) }; }
1332            if s != STATUS_OK {
1333                return Err(CoreError::binder(s, "transact:unregisterTaskStackListener"));
1334            }
1335            Ok(())
1336        }
1337    }
1338
1339    impl Drop for TaskStackListener {
1340        /// Best-effort deregistration from `activity_task`. Same deliberate
1341        /// non-release of the local strong ref as [`FpsListener`] — the
1342        /// userdata OwnedFd stays valid for any in-flight wake, and the
1343        /// framework-side registration is gone after the unregister.
1344        fn drop(&mut self) {
1345            let _ = self.unregister();
1346        }
1347    }
1348
1349    // ── RawBinderService ──────────────────────────────────────────────────────
1350
1351    /// Generic binder client for any named Android service.
1352    ///
1353    /// Handles its own `dlopen` on `libbinder_ndk.so`. Callers provide raw
1354    /// transaction codes (resolved via [`crate::dex::find_transaction_code`])
1355    /// and use [`RawBinderService::transact_bool`] /
1356    /// [`RawBinderService::transact_i32`] for typed round-trips.
1357    pub struct RawBinderService {
1358        _lib:    DlHandle,
1359        vt:      Vtable,
1360        service: OwnedBinder,
1361    }
1362    unsafe impl Send for RawBinderService {}
1363
1364    impl RawBinderService {
1365        /// Open a connection to the named service (e.g. `"power"`, `"batterystats"`).
1366        pub fn open(service_name: &str) -> Result<Self, CoreError> {
1367            use std::ffi::CString;
1368            let handle = unsafe {
1369                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
1370            };
1371            if handle.is_null() {
1372                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
1373            }
1374            let lib = DlHandle(handle);
1375            let vt = load_vtable(handle)?;
1376            let cs = CString::new(service_name)
1377                .map_err(|_| CoreError::binder(-1, "service_name:nul_byte"))?;
1378            let raw = unsafe { (vt.get_service)(cs.as_ptr()) };
1379            if raw.is_null() {
1380                return Err(CoreError::binder(-1, "AServiceManager_getService:null"));
1381            }
1382            let service = OwnedBinder { ptr: raw, dec_strong: vt.dec_strong };
1383            Ok(Self { _lib: lib, vt, service })
1384        }
1385
1386        /// Send a no-argument transaction; read exception header then bool reply.
1387        pub fn transact_bool(&self, code: u32) -> Result<bool, CoreError> {
1388            let out = self.raw_noarg(code)?;
1389            let r = ParcelReader { vt: &self.vt, parcel: &out };
1390            let ex = r.read_i32()?;
1391            if ex != EX_NONE { return Err(CoreError::binder(ex, "transact_bool:exception")); }
1392            if let Some(rb) = self.vt.read_bool {
1393                let mut v = false;
1394                let s = unsafe { rb(out.ptr as *const AParcel, &mut v) };
1395                if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readBool")); }
1396                Ok(v)
1397            } else {
1398                Ok(r.read_i32()? != 0)
1399            }
1400        }
1401
1402        /// Send a transaction with one i32 argument; discard reply.
1403        pub fn transact_i32(&self, code: u32, arg: i32) -> Result<(), CoreError> {
1404            let mut inp: *mut AParcel = std::ptr::null_mut();
1405            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut inp) };
1406            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
1407            let s = unsafe { (self.vt.write_int32)(inp, arg) };
1408            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_writeInt32")); }
1409            let mut out: *mut AParcel = std::ptr::null_mut();
1410            let s = unsafe { (self.vt.transact)(self.service.ptr, code, &mut inp, &mut out, 0) };
1411            if !out.is_null() { unsafe { (self.vt.parcel_delete)(out) }; }
1412            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
1413            Ok(())
1414        }
1415
1416        fn raw_noarg(&self, code: u32) -> Result<OwnedParcel, CoreError> {
1417            let mut inp: *mut AParcel = std::ptr::null_mut();
1418            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut inp) };
1419            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
1420            let mut out: *mut AParcel = std::ptr::null_mut();
1421            let s = unsafe { (self.vt.transact)(self.service.ptr, code, &mut inp, &mut out, 0) };
1422            let out = OwnedParcel { ptr: out, delete: self.vt.parcel_delete };
1423            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
1424            Ok(out)
1425        }
1426    }
1427}
1428
1429// ── Public re-exports ─────────────────────────────────────────────────────────
1430
1431#[cfg(target_os = "android")]
1432pub use imp::{ActivityManagerBinder, DisplayManagerBinder, FpsListener, RawBinderService, TaskStackListener, TxCodes, last_foreground_pid, resolve_tx_codes};
1433
1434// ── Non-Android stubs ─────────────────────────────────────────────────────────
1435
1436#[cfg(not(target_os = "android"))]
1437pub struct ActivityManagerBinder;
1438
1439#[cfg(not(target_os = "android"))]
1440impl ActivityManagerBinder {
1441    pub fn open() -> Result<Self, crate::CoreError> {
1442        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1443    }
1444    pub fn open_with_observer() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1445        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1446    }
1447    pub fn open_with_fgproc_observer() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1448        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1449    }
1450    pub fn get_focused_task(&self) -> Result<Option<(i32, Option<String>)>, crate::CoreError> {
1451        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1452    }
1453    pub fn get_focused_package(&self) -> Result<Option<String>, crate::CoreError> {
1454        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1455    }
1456    pub fn get_focused_task_id(&self) -> Result<Option<i32>, crate::CoreError> {
1457        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1458    }
1459}
1460
1461#[cfg(not(target_os = "android"))]
1462pub struct DisplayManagerBinder;
1463
1464#[cfg(not(target_os = "android"))]
1465impl DisplayManagerBinder {
1466    pub fn open_with_callback() -> Result<(Self, crate::reactor::Fd), crate::CoreError> {
1467        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1468    }
1469    pub fn is_interactive(&self) -> Result<bool, crate::CoreError> {
1470        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1471    }
1472}
1473
1474#[cfg(not(target_os = "android"))]
1475pub struct RawBinderService;
1476
1477#[cfg(not(target_os = "android"))]
1478impl RawBinderService {
1479    pub fn open(_service_name: &str) -> Result<Self, crate::CoreError> {
1480        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1481    }
1482    pub fn transact_bool(&self, _code: u32) -> Result<bool, crate::CoreError> {
1483        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1484    }
1485    pub fn transact_i32(&self, _code: u32, _arg: i32) -> Result<(), crate::CoreError> {
1486        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1487    }
1488}
1489
1490#[cfg(not(target_os = "android"))]
1491pub struct FpsListener;
1492
1493#[cfg(not(target_os = "android"))]
1494impl FpsListener {
1495    pub fn open() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1496        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1497    }
1498    pub fn register(&mut self, _task_id: i32) -> Result<(), crate::CoreError> {
1499        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1500    }
1501    pub fn unregister(&mut self) -> Result<(), crate::CoreError> {
1502        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1503    }
1504    pub fn last_fps(&self) -> Option<f32> {
1505        None
1506    }
1507    pub fn task_id(&self) -> Option<i32> {
1508        None
1509    }
1510}
1511
1512#[cfg(not(target_os = "android"))]
1513pub struct TaskStackListener;
1514
1515#[cfg(not(target_os = "android"))]
1516impl TaskStackListener {
1517    pub fn open() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1518        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1519    }
1520    pub fn register(&self) -> Result<(), crate::CoreError> {
1521        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1522    }
1523    pub fn unregister(&self) -> Result<(), crate::CoreError> {
1524        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1525    }
1526}
1527
1528#[cfg(not(target_os = "android"))]
1529pub struct TxCodes { pub observer_code: u32, pub query_code: u32, pub api_mode: u8, pub fg_code: u32 }
1530
1531#[cfg(not(target_os = "android"))]
1532pub fn resolve_tx_codes() -> Result<TxCodes, crate::CoreError> {
1533    Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1534}
1535
1536#[cfg(not(target_os = "android"))]
1537pub fn last_foreground_pid() -> i32 {
1538    0
1539}