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::Mutex;
33    use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicUsize, Ordering};
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 {
103        std::ptr::null_mut()
104    }
105    unsafe extern "C" fn am_on_destroy(_: *mut c_void) {}
106    unsafe extern "C" fn am_on_transact(
107        _: *mut AIBinder,
108        _: u32,
109        _: *const AParcel,
110        _: *mut AParcel,
111    ) -> BinderStatus {
112        STATUS_UNKNOWN_TRANSACTION
113    }
114
115    // IProcessObserver server callbacks
116    unsafe extern "C" fn obs_on_create(_: *mut c_void) -> *mut c_void {
117        std::ptr::null_mut()
118    }
119    unsafe extern "C" fn obs_on_destroy(_: *mut c_void) {}
120    unsafe extern "C" fn obs_on_transact(
121        _: *mut AIBinder,
122        code: u32,
123        _: *const AParcel,
124        _: *mut AParcel,
125    ) -> BinderStatus {
126        if code == OBS_FG_CODE.load(Ordering::Relaxed) {
127            // Write while holding the lock: the fd can only be closed while
128            // we hold it, so a revoke can never race us into a stale number.
129            if let Some(fd) = obs_eventfd_guard().as_ref() {
130                let val: u64 = 1;
131                unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
132            }
133        }
134        STATUS_OK
135    }
136
137    // IForegroundProcessObserver server callbacks. Two parcel layouts, selected
138    // by FGPROC_IPROC_MODE:
139    //  - mode 0 (stock): `onForegroundProcessChanged(int pid)` — single int32.
140    //  - mode 1 (custom ROMs without IForegroundProcessObserver): the ROM
141    //    repurposes `IProcessObserver.onForegroundActivitiesChanged` to deliver
142    //    `(int pid, int uid, int fg)`. The callback stores the pid and only
143    //    signals the eventfd when fg != 0 (a foreground transition), so
144    //    background transitions never cause the daemon to react.
145    unsafe extern "C" fn fgproc_on_create(_: *mut c_void) -> *mut c_void {
146        std::ptr::null_mut()
147    }
148    unsafe extern "C" fn fgproc_on_destroy(_: *mut c_void) {}
149    unsafe extern "C" fn fgproc_on_transact(
150        _: *mut AIBinder,
151        code: u32,
152        in_parcel: *const AParcel,
153        _: *mut AParcel,
154    ) -> BinderStatus {
155        if code != FGPROC_FG_CODE.load(Ordering::Relaxed) {
156            return STATUS_UNKNOWN_TRANSACTION;
157        }
158        // Read fn is published non-zero before the code/eventfd, so a matching
159        // code is never paired with an unset reader.
160        let read_addr = FGPROC_READ_I32.load(Ordering::Relaxed);
161        if read_addr != 0 {
162            let read_fn: unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus =
163                unsafe { std::mem::transmute(read_addr) };
164            if FGPROC_IPROC_MODE.load(Ordering::Relaxed) == 1 {
165                // IProcessObserver.onForegroundActivitiesChanged(pid, uid, fg)
166                let mut pid: i32 = 0;
167                let mut _uid: i32 = 0;
168                let mut fg: i32 = 0;
169                let mut ok = unsafe { read_fn(in_parcel, &mut pid) } == STATUS_OK;
170                ok &= unsafe { read_fn(in_parcel, &mut _uid) } == STATUS_OK;
171                ok &= unsafe { read_fn(in_parcel, &mut fg) } == STATUS_OK;
172                if ok {
173                    FGPROC_PID.store(pid, Ordering::Relaxed);
174                    // Only foreground transitions are actionable; suppress the
175                    // background transition entirely (fg == 0).
176                    if fg == 0 {
177                        return STATUS_OK;
178                    }
179                } else {
180                    return STATUS_OK;
181                }
182            } else {
183                let mut pid: i32 = 0;
184                if unsafe { read_fn(in_parcel, &mut pid) } == STATUS_OK {
185                    FGPROC_PID.store(pid, Ordering::Relaxed);
186                }
187            }
188        }
189        // Write while holding the lock: the fd can only be closed while we
190        // hold it, so a revoke can never race us into a stale number.
191        if let Some(fd) = fgproc_eventfd_guard().as_ref() {
192            let val: u64 = 1;
193            unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
194        }
195        STATUS_OK
196    }
197
198    /// The PID captured by the most recent `onForegroundProcessChanged`
199    /// callback (requires `open_with_fgproc_observer`).
200    pub fn last_foreground_pid() -> i32 {
201        FGPROC_PID.load(Ordering::Relaxed)
202    }
203
204    // ── String allocator ─────────────────────────────────────────────────────
205
206    /// Ceiling on a single parcel string regardless of the length the peer
207    /// advertises. Component names are at most a few hundred bytes; this bounds
208    /// the allocation so a malformed advertised length cannot drive a giant
209    /// `reserve_exact` (which would abort on OOM).
210    const MAX_BINDER_STRING_LEN: usize = 1024 * 1024;
211
212    unsafe extern "C" fn string_alloc(
213        cookie: *mut c_void,
214        length: i32,
215        buffer: *mut *mut c_char,
216    ) -> bool {
217        // Negative or oversized lengths are allocation failures: returning
218        // true with no usable buffer would hand the reader a dangling pointer,
219        // and an oversized reserve_exact would abort on OOM.
220        if length < 0 {
221            return false;
222        }
223        let len = length as usize;
224        if len > MAX_BINDER_STRING_LEN {
225            return false;
226        }
227        let s = unsafe { &mut *(cookie as *mut StringBuf) };
228        s.0.reserve_exact(len + 1);
229        unsafe { s.0.as_mut_vec().resize(len + 1, 0) };
230        unsafe { *buffer = s.0.as_mut_ptr() as *mut c_char };
231        true
232    }
233
234    struct StringBuf(String);
235    impl StringBuf {
236        fn new() -> Self {
237            Self(String::new())
238        }
239        fn finish(mut self) -> Option<String> {
240            if let Some(pos) = self.0.as_bytes().iter().position(|&b| b == 0) {
241                unsafe { self.0.as_mut_vec().truncate(pos) };
242            }
243            if self.0.is_empty() {
244                None
245            } else {
246                Some(self.0)
247            }
248        }
249    }
250
251    // ── Vtable ────────────────────────────────────────────────────────────────
252
253    struct Vtable {
254        get_service: unsafe extern "C" fn(*const c_char) -> *mut AIBinder,
255        class_define: unsafe extern "C" fn(
256            *const c_char,
257            unsafe extern "C" fn(*mut c_void) -> *mut c_void,
258            unsafe extern "C" fn(*mut c_void),
259            unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel) -> BinderStatus,
260        ) -> *mut AIBinder_Class,
261        associate_class: unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool,
262        new_binder: unsafe extern "C" fn(*const AIBinder_Class, *mut c_void) -> *mut AIBinder,
263        prepare_transaction: unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus,
264        transact: unsafe extern "C" fn(
265            *mut AIBinder,
266            u32,
267            *mut *mut AParcel,
268            *mut *mut AParcel,
269            u32,
270        ) -> BinderStatus,
271        dec_strong: unsafe extern "C" fn(*mut AIBinder),
272        parcel_delete: unsafe extern "C" fn(*mut AParcel),
273        read_int32: unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus,
274        read_string:
275            unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus,
276        write_strong_binder: unsafe extern "C" fn(*mut AParcel, *mut AIBinder) -> BinderStatus,
277        set_thread_pool_max: unsafe extern "C" fn(u32),
278        join_thread_pool: unsafe extern "C" fn(),
279        get_user_data: unsafe extern "C" fn(*const AIBinder) -> *mut c_void,
280        write_int32: unsafe extern "C" fn(*mut AParcel, i32) -> BinderStatus,
281        // Optional: only present on API 29+, but all modern Android has this
282        read_bool: Option<unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus>,
283    }
284
285    // ── RAII wrappers ─────────────────────────────────────────────────────────
286
287    struct DlHandle;
288    unsafe impl Send for DlHandle {}
289    impl Drop for DlHandle {
290        fn drop(&mut self) {
291            // Intentionally no dlclose: the binder thread pool spawned in
292            // open_with_observer() keeps executing library code until process
293            // exit. Unloading the library while that thread runs causes
294            // use-after-free. libbinder_ndk.so is never unloaded during the
295            // daemon lifetime; the OS reclaims it on exit.
296        }
297    }
298
299    struct OwnedParcel {
300        ptr: *mut AParcel,
301        delete: unsafe extern "C" fn(*mut AParcel),
302    }
303    impl Drop for OwnedParcel {
304        fn drop(&mut self) {
305            if !self.ptr.is_null() {
306                unsafe { (self.delete)(self.ptr) };
307            }
308        }
309    }
310
311    struct OwnedBinder {
312        ptr: *mut AIBinder,
313        dec_strong: unsafe extern "C" fn(*mut AIBinder),
314    }
315    unsafe impl Send for OwnedBinder {}
316    impl Drop for OwnedBinder {
317        fn drop(&mut self) {
318            if !self.ptr.is_null() {
319                unsafe { (self.dec_strong)(self.ptr) };
320            }
321        }
322    }
323
324    // ── dlsym helper ─────────────────────────────────────────────────────────
325
326    macro_rules! dlsym_fn {
327        ($handle:expr, $name:literal, $ty:ty) => {{
328            let sym =
329                unsafe { libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char) };
330            if sym.is_null() {
331                return Err(CoreError::binder(-1, concat!("dlsym:", $name)));
332            }
333            unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }
334        }};
335    }
336
337    macro_rules! dlsym_opt {
338        ($handle:expr, $name:literal, $ty:ty) => {{
339            let sym =
340                unsafe { libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char) };
341            if sym.is_null() {
342                None
343            } else {
344                Some(unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) })
345            }
346        }};
347    }
348
349    fn load_vtable(handle: *mut c_void) -> Result<Vtable, CoreError> {
350        Ok(Vtable {
351            get_service: dlsym_fn!(
352                handle,
353                "AServiceManager_getService",
354                unsafe extern "C" fn(*const c_char) -> *mut AIBinder
355            ),
356            class_define: dlsym_fn!(
357                handle,
358                "AIBinder_Class_define",
359                unsafe extern "C" fn(
360                    *const c_char,
361                    unsafe extern "C" fn(*mut c_void) -> *mut c_void,
362                    unsafe extern "C" fn(*mut c_void),
363                    unsafe extern "C" fn(
364                        *mut AIBinder,
365                        u32,
366                        *const AParcel,
367                        *mut AParcel,
368                    ) -> BinderStatus,
369                ) -> *mut AIBinder_Class
370            ),
371            associate_class: dlsym_fn!(
372                handle,
373                "AIBinder_associateClass",
374                unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool
375            ),
376            new_binder: dlsym_fn!(
377                handle,
378                "AIBinder_new",
379                unsafe extern "C" fn(*const AIBinder_Class, *mut c_void) -> *mut AIBinder
380            ),
381            prepare_transaction: dlsym_fn!(
382                handle,
383                "AIBinder_prepareTransaction",
384                unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus
385            ),
386            transact: dlsym_fn!(
387                handle,
388                "AIBinder_transact",
389                unsafe extern "C" fn(
390                    *mut AIBinder,
391                    u32,
392                    *mut *mut AParcel,
393                    *mut *mut AParcel,
394                    u32,
395                ) -> BinderStatus
396            ),
397            dec_strong: dlsym_fn!(
398                handle,
399                "AIBinder_decStrong",
400                unsafe extern "C" fn(*mut AIBinder)
401            ),
402            parcel_delete: dlsym_fn!(handle, "AParcel_delete", unsafe extern "C" fn(*mut AParcel)),
403            read_int32: dlsym_fn!(
404                handle,
405                "AParcel_readInt32",
406                unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus
407            ),
408            read_string: dlsym_fn!(
409                handle,
410                "AParcel_readString",
411                unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus
412            ),
413            write_strong_binder: dlsym_fn!(
414                handle,
415                "AParcel_writeStrongBinder",
416                unsafe extern "C" fn(*mut AParcel, *mut AIBinder) -> BinderStatus
417            ),
418            set_thread_pool_max: dlsym_fn!(
419                handle,
420                "ABinderProcess_setThreadPoolMaxThreadCount",
421                unsafe extern "C" fn(u32)
422            ),
423            join_thread_pool: dlsym_fn!(
424                handle,
425                "ABinderProcess_joinThreadPool",
426                unsafe extern "C" fn()
427            ),
428            get_user_data: dlsym_fn!(
429                handle,
430                "AIBinder_getUserData",
431                unsafe extern "C" fn(*const AIBinder) -> *mut c_void
432            ),
433            write_int32: dlsym_fn!(
434                handle,
435                "AParcel_writeInt32",
436                unsafe extern "C" fn(*mut AParcel, i32) -> BinderStatus
437            ),
438            read_bool: dlsym_opt!(
439                handle,
440                "AParcel_readBool",
441                unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus
442            ),
443        })
444    }
445
446    // ── ParcelReader ──────────────────────────────────────────────────────────
447
448    struct ParcelReader<'a> {
449        vt: &'a Vtable,
450        parcel: &'a OwnedParcel,
451    }
452
453    impl<'a> ParcelReader<'a> {
454        fn read_i32(&self) -> Result<i32, CoreError> {
455            let mut v = 0i32;
456            let s = unsafe { (self.vt.read_int32)(self.parcel.ptr, &mut v) };
457            if s != STATUS_OK {
458                return Err(CoreError::binder(s, "AParcel_readInt32"));
459            }
460            Ok(v)
461        }
462        fn read_string(&self) -> Result<Option<String>, CoreError> {
463            let mut buf = StringBuf::new();
464            let s = unsafe {
465                (self.vt.read_string)(
466                    self.parcel.ptr,
467                    &mut buf as *mut StringBuf as *mut c_void,
468                    string_alloc,
469                )
470            };
471            if s != STATUS_OK {
472                return Err(CoreError::binder(s, "AParcel_readString"));
473            }
474            Ok(buf.finish())
475        }
476        fn skip_i32s(&self, n: usize) -> Result<(), CoreError> {
477            for _ in 0..n {
478                self.read_i32()?;
479            }
480            Ok(())
481        }
482        fn skip_int_array(&self) -> Result<(), CoreError> {
483            let count = self.read_i32()?.max(0) as usize;
484            self.skip_i32s(count)
485        }
486        fn read_first_package_from_names(&self) -> Result<Option<String>, CoreError> {
487            let count = self.read_i32()?.max(0) as usize;
488            let mut first: Option<String> = None;
489            for _ in 0..count {
490                let s = self.read_string()?;
491                if first.is_none() {
492                    first = s.and_then(|c| c.split('/').next().map(str::to_owned));
493                }
494            }
495            Ok(first)
496        }
497    }
498
499    // ── ParcelWriter / transact helper ────────────────────────────────────────
500
501    struct ParcelWriter<'a> {
502        vt: &'a Vtable,
503        parcel: &'a OwnedParcel,
504    }
505
506    impl<'a> ParcelWriter<'a> {
507        fn write_i32(&self, v: i32) -> Result<(), CoreError> {
508            let s = unsafe { (self.vt.write_int32)(self.parcel.ptr, v) };
509            if s != STATUS_OK {
510                return Err(CoreError::binder(s, "AParcel_writeInt32"));
511            }
512            Ok(())
513        }
514        fn write_strong_binder(&self, b: *mut AIBinder) -> Result<(), CoreError> {
515            let s = unsafe { (self.vt.write_strong_binder)(self.parcel.ptr, b) };
516            if s != STATUS_OK {
517                return Err(CoreError::binder(s, "AParcel_writeStrongBinder"));
518            }
519            Ok(())
520        }
521    }
522
523    /// Prepare an input parcel, run `writes`, then transact. RAII on both
524    /// ends: a write error drops the input parcel (previously it leaked on the
525    /// write-error path, never reaching transact), and the reply parcel is
526    /// returned owned. The input parcel is transferred to `AIBinder_transact`
527    /// (the framework deletes it even on failure), so the wrapper records that
528    /// by nulling its pointer — never a double-delete.
529    fn transact_write(
530        vt: &Vtable,
531        binder: *mut AIBinder,
532        code: u32,
533        writes: impl FnOnce(&ParcelWriter<'_>) -> Result<(), CoreError>,
534    ) -> Result<OwnedParcel, CoreError> {
535        let mut in_ptr: *mut AParcel = std::ptr::null_mut();
536        let s = unsafe { (vt.prepare_transaction)(binder, &mut in_ptr) };
537        if s != STATUS_OK {
538            return Err(CoreError::binder(s, "AIBinder_prepareTransaction"));
539        }
540        let mut inp = OwnedParcel {
541            ptr: in_ptr,
542            delete: vt.parcel_delete,
543        };
544        {
545            let writer = ParcelWriter { vt, parcel: &inp };
546            writes(&writer)?;
547        }
548        let mut out_ptr: *mut AParcel = std::ptr::null_mut();
549        let s = unsafe { (vt.transact)(binder, code, &mut inp.ptr, &mut out_ptr, 0) };
550        // The framework owns (and deletes) the input parcel from here.
551        inp.ptr = std::ptr::null_mut();
552        let out = OwnedParcel {
553            ptr: out_ptr,
554            delete: vt.parcel_delete,
555        };
556        if s != STATUS_OK {
557            return Err(CoreError::binder(s, "AIBinder_transact"));
558        }
559        Ok(out)
560    }
561
562    // ── Response parsers ──────────────────────────────────────────────────────
563
564    fn parse_stack_info_body(r: &ParcelReader<'_>) -> Result<Option<String>, CoreError> {
565        r.skip_i32s(5)?;
566        r.skip_int_array()?;
567        r.read_first_package_from_names()
568    }
569
570    // RootTaskInfo → (taskId, first childTaskName package). Walks the parcel
571    // once: prefix (bounds, childTaskIds), captures the first package from
572    // childTaskNames, then skips childTaskBounds / childTaskUserIds / visible /
573    // position / TaskInfo.userId to reach taskId — never touching the
574    // Intent/TaskInfo tail. taskId and pkg come from the same transaction, so
575    // callers pair them without a second (racy) round-trip.
576    fn parse_root_task_info_task(r: &ParcelReader<'_>) -> Result<(i32, Option<String>), CoreError> {
577        let scratch = r.read_i32()?;
578        if scratch != 0 {
579            r.skip_i32s(4)?;
580        }
581        r.skip_int_array()?; // childTaskIds
582        let pkg = r.read_first_package_from_names()?; // childTaskNames → pkg
583        // childTaskBounds: typed Rect array (nullable) — read count, skip 4 per entry
584        let bounds_count = r.read_i32()?;
585        let n = if bounds_count < 0 {
586            0
587        } else {
588            bounds_count as usize
589        };
590        for _ in 0..n {
591            let entry = r.read_i32()?;
592            if entry != 0 {
593                r.skip_i32s(4)?;
594            }
595        }
596        r.skip_int_array()?; // childTaskUserIds
597        r.skip_i32s(2)?; // visible, position
598        r.skip_i32s(1)?; // TaskInfo.userId
599        let task_id = r.read_i32()?;
600        Ok((task_id, pkg))
601    }
602
603    // ── Tx code resolution ────────────────────────────────────────────────────
604
605    pub struct TxCodes {
606        pub observer_code: u32,
607        pub query_code: u32,
608        pub api_mode: u8, // 1 = RootTaskInfo, 2 = StackInfo
609        pub fg_code: u32,
610    }
611
612    pub fn resolve_tx_codes() -> Result<TxCodes, CoreError> {
613        let (obs, query, api, fg) = dex::resolve_tx_codes_from_dex()
614            .ok_or_else(|| CoreError::binder(-1, "tx_code_resolution:dex_parse_failed"))?;
615        Ok(TxCodes {
616            observer_code: obs,
617            query_code: query,
618            api_mode: api,
619            fg_code: fg,
620        })
621    }
622
623    // ── ActivityManagerBinder ─────────────────────────────────────────────────
624
625    pub struct ActivityManagerBinder {
626        _lib: DlHandle,
627        vt: Vtable,
628        _class: *mut AIBinder_Class,
629        service: OwnedBinder,
630        tx_code: u32,
631        legacy: bool,
632    }
633    unsafe impl Send for ActivityManagerBinder {}
634
635    impl ActivityManagerBinder {
636        fn open_inner(
637            handle: *mut c_void,
638        ) -> Result<(DlHandle, Vtable, *mut AIBinder_Class, OwnedBinder), CoreError> {
639            let lib = DlHandle;
640            let vt = load_vtable(handle)?;
641
642            let am_class = unsafe {
643                (vt.class_define)(
644                    AM_DESCRIPTOR.as_ptr() as *const c_char,
645                    am_on_create,
646                    am_on_destroy,
647                    am_on_transact,
648                )
649            };
650            if am_class.is_null() {
651                return Err(CoreError::binder(-1, "AIBinder_Class_define:AM"));
652            }
653
654            let raw = unsafe { (vt.get_service)(ACTIVITY_SERVICE.as_ptr() as *const c_char) };
655            if raw.is_null() {
656                return Err(CoreError::binder(-1, "AServiceManager_getService:activity"));
657            }
658            unsafe { (vt.associate_class)(raw, am_class) };
659
660            let service = OwnedBinder {
661                ptr: raw,
662                dec_strong: vt.dec_strong,
663            };
664            Ok((lib, vt, am_class, service))
665        }
666
667        fn dlopen_libbinder() -> Result<*mut c_void, CoreError> {
668            use std::os::raw::c_char;
669            let handle = unsafe {
670                libc::dlopen(
671                    LIBBINDER_PATH.as_ptr() as *const c_char,
672                    libc::RTLD_NOW | libc::RTLD_LOCAL,
673                )
674            };
675            if handle.is_null() {
676                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
677            }
678            Ok(handle)
679        }
680
681        /// Open ActivityManager binder (polling mode — no observer).
682        /// Resolves the query tx code from cache or DEX.
683        pub fn open() -> Result<Self, CoreError> {
684            let handle = Self::dlopen_libbinder()?;
685            let (lib, vt, class, service) = Self::open_inner(handle)?;
686            let codes = resolve_tx_codes()?;
687            let legacy = codes.api_mode == 2;
688            Ok(Self {
689                _lib: lib,
690                vt,
691                _class: class,
692                service,
693                tx_code: codes.query_code,
694                legacy,
695            })
696        }
697
698        /// Open ActivityManager binder and register as IProcessObserver.
699        ///
700        /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
701        /// callback fd. It becomes readable whenever `onForegroundActivitiesChanged`
702        /// fires. Caller must add it to epoll and may close it at any time — the
703        /// callback keeps writing to the core's copy, so closing the returned
704        /// fd never invalidates the notification path (C2). After the event
705        /// fires, call `get_focused_package`.
706        pub fn open_with_observer() -> Result<(Self, OwnedFd), CoreError> {
707            let handle = Self::dlopen_libbinder()?;
708            let (lib, vt, am_class, service) = Self::open_inner(handle)?;
709            let codes = resolve_tx_codes()?;
710            let legacy = codes.api_mode == 2;
711
712            // Create eventfd for callback → epoll bridge. Ownership stays in the
713            // core for the observer lifetime; the consumer receives a dup below.
714            let owned = unsafe {
715                let raw = libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC);
716                if raw < 0 {
717                    return Err(CoreError::sys(*libc::__errno(), "eventfd"));
718                }
719                OwnedFd::from_raw_fd(raw)
720            };
721
722            // Define IProcessObserver class (we're the server)
723            let obs_class = unsafe {
724                (vt.class_define)(
725                    OBS_DESCRIPTOR.as_ptr() as *const c_char,
726                    obs_on_create,
727                    obs_on_destroy,
728                    obs_on_transact,
729                )
730            };
731            if obs_class.is_null() {
732                return Err(CoreError::binder(-1, "AIBinder_Class_define:Observer"));
733            }
734
735            // Instantiate our observer binder object
736            let obs_binder = unsafe { (vt.new_binder)(obs_class, std::ptr::null_mut()) };
737            if obs_binder.is_null() {
738                return Err(CoreError::binder(-1, "AIBinder_new:Observer"));
739            }
740            unsafe { (vt.associate_class)(obs_binder, obs_class) };
741
742            // Call registerProcessObserver(observer)
743            let _ = transact_write(&vt, service.ptr, codes.observer_code, |w| {
744                w.write_strong_binder(obs_binder)
745            })?;
746
747            // Consumer dup — made before publishing, so an error path drops the
748            // owned fd without ever leaving a stale handle for the callback.
749            let consumer = owned
750                .try_clone()
751                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:observer"))?;
752
753            // Publish fg_code and the core-owned eventfd for the callback
754            OBS_FG_CODE.store(codes.fg_code, Ordering::Relaxed);
755            *obs_eventfd_guard() = Some(owned);
756
757            // Start binder thread pool — blocks forever in background thread
758            unsafe { (vt.set_thread_pool_max)(0) };
759            let join_fn = vt.join_thread_pool;
760            std::thread::spawn(move || unsafe { join_fn() });
761
762            let binder = Self {
763                _lib: lib,
764                vt,
765                _class: am_class,
766                service,
767                tx_code: codes.query_code,
768                legacy,
769            };
770            Ok((binder, consumer))
771        }
772
773        /// Open ActivityManager binder and register as the foreground process
774        /// observer.
775        ///
776        /// The authoritative foreground PID is delivered in the callback; this
777        /// is the low-noise foreground source. Two ROM variants are supported
778        /// and selected automatically:
779        ///
780        /// - Stock: `IForegroundProcessObserver.onForegroundProcessChanged`
781        ///   delivers a single `int pid`.
782        /// - Custom ROMs that dropped that interface instead deliver `(int pid,
783        ///   int uid, int fg)` through the repurposed
784        ///   `IProcessObserver.onForegroundActivitiesChanged`; this registers
785        ///   via `registerProcessObserver` and only signals on `fg != 0`.
786        ///
787        /// The callback stores the PID (readable via [`last_foreground_pid`])
788        /// and signals the returned eventfd.
789        ///
790        /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
791        /// callback fd. It becomes readable whenever a foreground process
792        /// change fires. Same lifetime contract as
793        /// [`ActivityManagerBinder::open_with_observer`] (C2): the core owns
794        /// the eventfd and the callback only ever writes to that copy, so
795        /// closing the returned dup never invalidates the notification path.
796        pub fn open_with_fgproc_observer() -> Result<(Self, OwnedFd), CoreError> {
797            let handle = Self::dlopen_libbinder()?;
798            let (lib, vt, am_class, service) = Self::open_inner(handle)?;
799
800            // Resolve the foreground-observer tx codes. Prefer the stock
801            // IForegroundProcessObserver path; fall back to the custom
802            // IProcessObserver pid-carrying form on ROMs that dropped it.
803            // mode 0 = stock single-int callback, mode 1 = (pid, uid, fg).
804            let (register_code, fgproc_code, mode, descriptor): (u32, u32, u32, &[u8]) =
805                match crate::dex::resolve_fgproc_codes() {
806                    Some((r, c)) => (r, c, 0, FGPROC_DESCRIPTOR),
807                    None => match crate::dex::resolve_fgproc_codes_fallback() {
808                        Some((r, c)) => (r, c, 1, OBS_DESCRIPTOR),
809                        None => {
810                            return Err(CoreError::binder(
811                                -1,
812                                "tx_code_resolution:fgproc_dex_parse_failed",
813                            ));
814                        }
815                    },
816                };
817
818            // Create eventfd for callback → epoll bridge. Ownership stays in the
819            // core for the observer lifetime; the consumer receives a dup below.
820            let owned = unsafe {
821                let raw = libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC);
822                if raw < 0 {
823                    return Err(CoreError::sys(*libc::__errno(), "eventfd"));
824                }
825                OwnedFd::from_raw_fd(raw)
826            };
827
828            // Define our observer class (we're the server). The descriptor must
829            // match whichever interface we actually register as.
830            let obs_class = unsafe {
831                (vt.class_define)(
832                    descriptor.as_ptr() as *const c_char,
833                    fgproc_on_create,
834                    fgproc_on_destroy,
835                    fgproc_on_transact,
836                )
837            };
838            if obs_class.is_null() {
839                return Err(CoreError::binder(
840                    -1,
841                    "AIBinder_Class_define:FGProcessObserver",
842                ));
843            }
844
845            // Instantiate our observer binder object
846            let obs_binder = unsafe { (vt.new_binder)(obs_class, std::ptr::null_mut()) };
847            if obs_binder.is_null() {
848                return Err(CoreError::binder(-1, "AIBinder_new:FGProcessObserver"));
849            }
850            unsafe { (vt.associate_class)(obs_binder, obs_class) };
851
852            // Call registerForegroundProcessObserver(observer) or the fallback
853            // registerProcessObserver(observer) depending on resolved mode.
854            let _ = transact_write(&vt, service.ptr, register_code, |w| {
855                w.write_strong_binder(obs_binder)
856            })?;
857
858            // Consumer dup — made before publishing, so an error path drops the
859            // owned fd without ever leaving a stale handle for the callback.
860            let consumer = owned.try_clone().map_err(|e| {
861                CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:fgproc_observer")
862            })?;
863
864            // Publish reader fn, mode, fg code, pid base, and the core-owned
865            // eventfd for the callback. Reader and mode are published first so
866            // the callback never sees a matching code with an unset reader or
867            // mode (C2-adjacent init order).
868            FGPROC_READ_I32.store(vt.read_int32 as usize, Ordering::Relaxed);
869            FGPROC_IPROC_MODE.store(mode, Ordering::Relaxed);
870            FGPROC_FG_CODE.store(fgproc_code, Ordering::Relaxed);
871            FGPROC_PID.store(0, Ordering::Relaxed);
872            *fgproc_eventfd_guard() = Some(owned);
873
874            // Start binder thread pool — blocks forever in background thread
875            unsafe { (vt.set_thread_pool_max)(0) };
876            let join_fn = vt.join_thread_pool;
877            std::thread::spawn(move || unsafe { join_fn() });
878
879            let binder = Self {
880                _lib: lib,
881                vt,
882                _class: am_class,
883                service,
884                tx_code: 0,
885                legacy: false,
886            };
887            Ok((binder, consumer))
888        }
889
890        fn do_transact(&self) -> Result<OwnedParcel, CoreError> {
891            transact_write(&self.vt, self.service.ptr, self.tx_code, |_| Ok(()))
892        }
893
894        /// The focused root task's `(taskId, topActivity package)` from a single
895        /// txn-31 transaction. Outer `None` = no focused root task (or legacy
896        /// API 29, where the reply is `StackInfo` and carries no taskId); inner
897        /// `None` = task known but no package in `childTaskNames`. Both values
898        /// come from the same parcel, so the registration key and the report
899        /// tag can never diverge.
900        pub fn get_focused_task(&self) -> Result<Option<(i32, Option<String>)>, CoreError> {
901            if self.legacy {
902                // StackInfo has no taskId — report None rather than a wrong id.
903                return Ok(None);
904            }
905            let out = self.do_transact()?;
906            let r = ParcelReader {
907                vt: &self.vt,
908                parcel: &out,
909            };
910            let ex = r.read_i32()?;
911            if ex != EX_NONE {
912                return Err(CoreError::binder(ex, "getFocusedTask:exception"));
913            }
914            let present = r.read_i32()?;
915            if present == 0 {
916                return Ok(None);
917            }
918            Ok(Some(parse_root_task_info_task(&r)?))
919        }
920
921        /// The `topActivity` package of the focused root task (legacy API 29
922        /// builds use `StackInfo` and still resolve the package). Thin wrapper
923        /// over [`ActivityManagerBinder::get_focused_task`].
924        pub fn get_focused_package(&self) -> Result<Option<String>, CoreError> {
925            if self.legacy {
926                let out = self.do_transact()?;
927                let r = ParcelReader {
928                    vt: &self.vt,
929                    parcel: &out,
930                };
931                let ex = r.read_i32()?;
932                if ex != EX_NONE {
933                    return Err(CoreError::binder(ex, "getFocusedTask:exception"));
934                }
935                let present = r.read_i32()?;
936                if present == 0 {
937                    return Ok(None);
938                }
939                return parse_stack_info_body(&r);
940            }
941            Ok(self.get_focused_task()?.map(|(_, pkg)| pkg).flatten())
942        }
943
944        /// The `taskId` of the currently focused root task. Thin wrapper over
945        /// [`ActivityManagerBinder::get_focused_task`]; returns `None` when there
946        /// is no focused root task or on legacy API 29 builds.
947        pub fn get_focused_task_id(&self) -> Result<Option<i32>, CoreError> {
948            Ok(self.get_focused_task()?.map(|(task_id, _)| task_id))
949        }
950    }
951
952    // ── DisplayManagerBinder ─────────────────────────────────────────────────
953
954    const DISPLAY_SERVICE: &[u8] = b"display\0";
955    const CALLBACK_DESCRIPTOR: &[u8] = b"android.hardware.display.IDisplayManagerCallback\0";
956    const POWER_SERVICE: &[u8] = b"power\0";
957
958    const TX_DISPLAY_REGISTER_CALLBACK: u32 = 4;
959
960    // Core owns the callback eventfd; the consumer gets a dup and may close it
961    // freely. Same lifetime discipline as the ActivityManager observer (C2).
962    static DISP_EVENTFD: Mutex<Option<OwnedFd>> = Mutex::new(None);
963
964    fn disp_eventfd_guard() -> std::sync::MutexGuard<'static, Option<OwnedFd>> {
965        DISP_EVENTFD.lock().unwrap_or_else(|p| p.into_inner())
966    }
967
968    unsafe extern "C" fn disp_cb_on_create(_: *mut c_void) -> *mut c_void {
969        std::ptr::null_mut()
970    }
971    unsafe extern "C" fn disp_cb_on_destroy(_: *mut c_void) {}
972    unsafe extern "C" fn disp_cb_on_transact(
973        _: *mut AIBinder,
974        code: u32,
975        _: *const AParcel,
976        _: *mut AParcel,
977    ) -> BinderStatus {
978        if code == 1 {
979            if let Some(fd) = disp_eventfd_guard().as_ref() {
980                let val: u64 = 1;
981                unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
982            }
983        }
984        STATUS_OK
985    }
986
987    pub struct DisplayManagerBinder {
988        _lib: DlHandle,
989        vt: Vtable,
990        power: Option<OwnedBinder>,
991        is_interactive_tx: u32,
992    }
993    unsafe impl Send for DisplayManagerBinder {}
994
995    impl DisplayManagerBinder {
996        pub fn open_with_callback() -> Result<(Self, crate::reactor::Fd), CoreError> {
997            let handle = unsafe {
998                libc::dlopen(
999                    LIBBINDER_PATH.as_ptr() as *const c_char,
1000                    libc::RTLD_NOW | libc::RTLD_LOCAL,
1001                )
1002            };
1003            if handle.is_null() {
1004                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
1005            }
1006            let lib = DlHandle;
1007            let vt = load_vtable(handle)?;
1008
1009            // Blocking eventfd (no EFD_NONBLOCK) — callback writes, caller's
1010            // read_u64_blocking() waits. The core owns it for the callback's
1011            // lifetime; the consumer receives a dup below (C2).
1012            let owned = unsafe {
1013                let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
1014                if raw < 0 {
1015                    return Err(CoreError::sys(*libc::__errno(), "eventfd"));
1016                }
1017                OwnedFd::from_raw_fd(raw)
1018            };
1019
1020            // Get display service (no class_define needed for client-only)
1021            let raw_display =
1022                unsafe { (vt.get_service)(DISPLAY_SERVICE.as_ptr() as *const c_char) };
1023            if raw_display.is_null() {
1024                return Err(CoreError::binder(-1, "AServiceManager_getService:display"));
1025            }
1026            let display = OwnedBinder {
1027                ptr: raw_display,
1028                dec_strong: vt.dec_strong,
1029            };
1030
1031            // Define IDisplayManagerCallback (we're the server receiving callbacks)
1032            let cb_class = unsafe {
1033                (vt.class_define)(
1034                    CALLBACK_DESCRIPTOR.as_ptr() as *const c_char,
1035                    disp_cb_on_create,
1036                    disp_cb_on_destroy,
1037                    disp_cb_on_transact,
1038                )
1039            };
1040            if cb_class.is_null() {
1041                return Err(CoreError::binder(
1042                    -1,
1043                    "AIBinder_Class_define:DisplayCallback",
1044                ));
1045            }
1046
1047            let cb_binder = unsafe { (vt.new_binder)(cb_class, std::ptr::null_mut()) };
1048            if cb_binder.is_null() {
1049                return Err(CoreError::binder(-1, "AIBinder_new:DisplayCallback"));
1050            }
1051
1052            // registerCallback(callback) — tx 4
1053            let _ = transact_write(&vt, display.ptr, TX_DISPLAY_REGISTER_CALLBACK, |w| {
1054                w.write_strong_binder(cb_binder)
1055            })?;
1056
1057            // Optional: grab power service for is_interactive()
1058            let power = {
1059                let raw = unsafe { (vt.get_service)(POWER_SERVICE.as_ptr() as *const c_char) };
1060                if raw.is_null() {
1061                    None
1062                } else {
1063                    Some(OwnedBinder {
1064                        ptr: raw,
1065                        dec_strong: vt.dec_strong,
1066                    })
1067                }
1068            };
1069
1070            // Resolve isInteractive tx code from DEX at open time
1071            let is_interactive_tx = crate::dex::resolve_is_interactive_tx()
1072                .ok_or_else(|| CoreError::binder(-1, "dex:TRANSACTION_isInteractive not found"))?;
1073
1074            // Consumer dup — made before publishing, so an error path drops the
1075            // owned fd without ever leaving a stale handle for the callback.
1076            let efd_owned = owned
1077                .try_clone()
1078                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:display"))
1079                .and_then(|dup| unsafe {
1080                    crate::reactor::Fd::from_owned_raw_fd(dup.into_raw_fd(), "display.efd")
1081                        .map_err(|_| CoreError::binder(-1, "Fd::from_owned_raw_fd:display.efd"))
1082                })?;
1083
1084            // Publish the core-owned eventfd for the callback
1085            *disp_eventfd_guard() = Some(owned);
1086
1087            // Join binder thread pool so callbacks can fire
1088            unsafe { (vt.set_thread_pool_max)(0) };
1089            let join_fn = vt.join_thread_pool;
1090            std::thread::spawn(move || unsafe { join_fn() });
1091
1092            Ok((
1093                Self {
1094                    _lib: lib,
1095                    vt,
1096                    power,
1097                    is_interactive_tx,
1098                },
1099                efd_owned,
1100            ))
1101        }
1102
1103        pub fn is_interactive(&self) -> Result<bool, CoreError> {
1104            let power = self
1105                .power
1106                .as_ref()
1107                .ok_or_else(|| CoreError::binder(-1, "power:unavailable"))?;
1108            let out = transact_write(&self.vt, power.ptr, self.is_interactive_tx, |_| Ok(()))?;
1109            let r = ParcelReader {
1110                vt: &self.vt,
1111                parcel: &out,
1112            };
1113            let ex = r.read_i32()?;
1114            if ex != EX_NONE {
1115                return Err(CoreError::binder(ex, "isInteractive:exception"));
1116            }
1117            if let Some(rb) = self.vt.read_bool {
1118                let mut v = false;
1119                let s = unsafe { rb(out.ptr as *const AParcel, &mut v) };
1120                if s != STATUS_OK {
1121                    return Err(CoreError::binder(s, "readBool:isInteractive"));
1122                }
1123                Ok(v)
1124            } else {
1125                Ok(r.read_i32()? != 0)
1126            }
1127        }
1128    }
1129
1130    // ── FpsListener (task FPS callback) ───────────────────────────────────────
1131
1132    const WINDOW_SERVICE: &[u8] = b"window\0";
1133    const WM_DESCRIPTOR: &[u8] = b"android.view.IWindowManager\0";
1134    const FPS_DESCRIPTOR: &[u8] = b"android.window.ITaskFpsCallback\0";
1135    // The last reported FPS (bit pattern of the f32) is published before the
1136    // eventfd is signalled, so the consumer never reads a stale value. The wake
1137    // eventfd is per-instance (same pattern as TaskStackListener): it is handed
1138    // to AIBinder_new as the callback binder's userdata, so a second
1139    // FpsListener can never rewire an earlier registration's wake into its own
1140    // fd (the callback resolves its own binder's fd via AIBinder_getUserData).
1141    static FPS_VALUE: AtomicU32 = AtomicU32::new(0);
1142    // Distinct from FPS_VALUE's bits: `0.0f32` has bit pattern 0, so a "not
1143    // seen" sentinel of 0 would misread a genuine idle (0-FPS) report as "no
1144    // report yet" — swallowing the sample and (downstream) leaving the first-
1145    // report-after-swap drop armed. The seen flag disambiguates.
1146    static FPS_SEEN: AtomicBool = AtomicBool::new(false);
1147    static FPS_CODE: AtomicU32 = AtomicU32::new(0);
1148    static FPS_READ_I32: AtomicUsize = AtomicUsize::new(0);
1149
1150    // No-op callbacks for the client-only IWindowManager class (we never serve
1151    // transactions on the `window` binder — the class exists only to satisfy
1152    // AIBinder_prepareTransaction's remote-transaction contract).
1153    unsafe extern "C" fn wm_on_create(_: *mut c_void) -> *mut c_void {
1154        std::ptr::null_mut()
1155    }
1156    unsafe extern "C" fn wm_on_destroy(_: *mut c_void) {}
1157    unsafe extern "C" fn wm_on_transact(
1158        _: *mut AIBinder,
1159        _: u32,
1160        _: *const AParcel,
1161        _: *mut AParcel,
1162    ) -> BinderStatus {
1163        STATUS_OK
1164    }
1165
1166    // The per-instance wake eventfd is this callback binder's userdata, so
1167    // onCreate must return the args passed to AIBinder_new — AIBinder_getUserData
1168    // returns exactly that value — and onDestroy must reclaim the box (same
1169    // pattern as TaskStackListener). Returning null here would make
1170    // AIBinder_getUserData return null, silently breaking the FPS wake.
1171    unsafe extern "C" fn fps_on_create(userdata: *mut c_void) -> *mut c_void {
1172        userdata
1173    }
1174    unsafe extern "C" fn fps_on_destroy(userdata: *mut c_void) {
1175        if !userdata.is_null() {
1176            unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
1177        }
1178    }
1179    unsafe extern "C" fn fps_on_transact(
1180        binder: *mut AIBinder,
1181        code: u32,
1182        in_parcel: *const AParcel,
1183        _: *mut AParcel,
1184    ) -> BinderStatus {
1185        if code != FPS_CODE.load(Ordering::Relaxed) {
1186            return STATUS_UNKNOWN_TRANSACTION;
1187        }
1188        // Reader is published non-zero before the code, so a matching code is
1189        // never paired with an unset reader.
1190        let read_addr = FPS_READ_I32.load(Ordering::Relaxed);
1191        if read_addr != 0 {
1192            let read_fn: unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus =
1193                unsafe { std::mem::transmute(read_addr) };
1194            let mut bits: i32 = 0;
1195            if unsafe { read_fn(in_parcel, &mut bits) } == STATUS_OK {
1196                // Publish the value before signalling so the consumer always
1197                // sees the value that triggered the wakeup. Non-finite/negative
1198                // reports are normalized to 0.0 first (L5) — the stream must
1199                // never carry "NaN"/"inf".
1200                FPS_VALUE.store(sanitize_fps(bits as u32), Ordering::Relaxed);
1201                FPS_SEEN.store(true, Ordering::Release);
1202                // Per-instance wake: the eventfd is this callback binder's
1203                // userdata (see FpsListener::open), so two FpsListeners never
1204                // cross-wire their wakes. A missing slot means the process-wide
1205                // AIBinder_getUserData symbol has not been cached — drop the
1206                // signal rather than risk a stale fd.
1207                let get_user_data = GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner());
1208                if let Some(get_user_data) = *get_user_data {
1209                    let userdata = unsafe { get_user_data(binder) };
1210                    if !userdata.is_null() {
1211                        let efd = userdata as *mut OwnedFd;
1212                        let val: u64 = 1;
1213                        unsafe {
1214                            libc::write((*efd).as_raw_fd(), &val as *const u64 as *const c_void, 8)
1215                        };
1216                    }
1217                }
1218            }
1219        }
1220        STATUS_OK
1221    }
1222
1223    /// Push-based per-task FPS listener registered with `WindowManager`.
1224    ///
1225    /// Uses `IWindowManager.registerTaskFpsCallback(taskId, callback)`; the
1226    /// daemon hosts the `ITaskFpsCallback` server object and receives
1227    /// `onFpsReported(float)` one-way transactions from the `FpsReporter` at
1228    /// most every ~500 ms.
1229    ///
1230    /// The registering UID must hold `ACCESS_FPS_COUNTER` (signature|privileged)
1231    /// — this process typically runs as shell (uid 2000) via `su`.
1232    ///
1233    /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
1234    /// callback fd. It becomes readable whenever `onFpsReported` fires; call
1235    /// [`FpsListener::last_fps`] after the event to read the value.
1236    pub struct FpsListener {
1237        _lib: DlHandle,
1238        vt: Vtable,
1239        window: OwnedBinder,
1240        cb_binder: *mut AIBinder,
1241        _wm_class: *mut AIBinder_Class,
1242        register_code: u32,
1243        unregister_code: u32,
1244        task_id: i32,
1245    }
1246    unsafe impl Send for FpsListener {}
1247
1248    impl FpsListener {
1249        /// Open WindowManager and define the `ITaskFpsCallback` server object.
1250        ///
1251        /// Resolves the three tx codes from DEX. Does **not** register a task
1252        /// yet — call [`FpsListener::register`] once a taskId is known. Starts
1253        /// the binder thread pool so `onFpsReported` can fire.
1254        pub fn open() -> Result<(Self, OwnedFd), CoreError> {
1255            let handle = unsafe {
1256                libc::dlopen(
1257                    LIBBINDER_PATH.as_ptr() as *const c_char,
1258                    libc::RTLD_NOW | libc::RTLD_LOCAL,
1259                )
1260            };
1261            if handle.is_null() {
1262                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
1263            }
1264            let lib = DlHandle;
1265            let vt = load_vtable(handle)?;
1266
1267            let (register_code, unregister_code, on_fps_code) = crate::dex::resolve_fps_codes()
1268                .ok_or_else(|| {
1269                    CoreError::binder(-1, "dex:TRANSACTION_registerTaskFpsCallback not found")
1270                })?;
1271
1272            let window = {
1273                let raw = unsafe { (vt.get_service)(WINDOW_SERVICE.as_ptr() as *const c_char) };
1274                if raw.is_null() {
1275                    return Err(CoreError::binder(-1, "AServiceManager_getService:window"));
1276                }
1277                OwnedBinder {
1278                    ptr: raw,
1279                    dec_strong: vt.dec_strong,
1280                }
1281            };
1282
1283            // Remote transactions require a class on the binder (same
1284            // AIBinder_prepareTransaction contract as the AM service above).
1285            let wm_class = unsafe {
1286                (vt.class_define)(
1287                    WM_DESCRIPTOR.as_ptr() as *const c_char,
1288                    wm_on_create,
1289                    wm_on_destroy,
1290                    wm_on_transact,
1291                )
1292            };
1293            if wm_class.is_null() {
1294                return Err(CoreError::binder(
1295                    -1,
1296                    "AIBinder_Class_define:IWindowManager",
1297                ));
1298            }
1299            unsafe { (vt.associate_class)(window.ptr, wm_class) };
1300
1301            let cb_class = unsafe {
1302                (vt.class_define)(
1303                    FPS_DESCRIPTOR.as_ptr() as *const c_char,
1304                    fps_on_create,
1305                    fps_on_destroy,
1306                    fps_on_transact,
1307                )
1308            };
1309            if cb_class.is_null() {
1310                return Err(CoreError::binder(
1311                    -1,
1312                    "AIBinder_Class_define:ITaskFpsCallback",
1313                ));
1314            }
1315
1316            // Nonblocking eventfd — the callback only ever writes to it (an
1317            // eventfd write never blocks), while epoll-based consumers register
1318            // it edge-triggered and drain to EAGAIN, so a blocking fd would
1319            // wedge the consumer's drain loop. Matches the obs/fgproc observer
1320            // eventfds. Each instance owns its own fd; it is handed to the
1321            // callback binder as userdata (per-instance routing, no process-wide
1322            // static) and the consumer receives a dup below (C2).
1323            let owned = unsafe {
1324                let raw = libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC);
1325                if raw < 0 {
1326                    return Err(CoreError::sys(*libc::__errno(), "eventfd"));
1327                }
1328                OwnedFd::from_raw_fd(raw)
1329            };
1330
1331            let consumer = owned
1332                .try_clone()
1333                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:fps"))?;
1334
1335            let userdata = Box::into_raw(Box::new(owned)) as *mut c_void;
1336            let cb_binder = unsafe { (vt.new_binder)(cb_class, userdata) };
1337            if cb_binder.is_null() {
1338                // Reclaim the userdata box handed to AIBinder_new before bailing.
1339                unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
1340                return Err(CoreError::binder(-1, "AIBinder_new:ITaskFpsCallback"));
1341            }
1342            unsafe { (vt.associate_class)(cb_binder, cb_class) };
1343
1344            // Publish the reader and code before the eventfd registration; a
1345            // matching code is never paired with an unset reader (C2-adjacent).
1346            FPS_READ_I32.store(vt.read_int32 as usize, Ordering::Relaxed);
1347            FPS_SEEN.store(false, Ordering::Relaxed);
1348            FPS_CODE.store(on_fps_code, Ordering::Relaxed);
1349            FPS_VALUE.store(0, Ordering::Relaxed);
1350            // The callback resolves AIBinder_getUserData from this vtable; the
1351            // symbol address is process-wide, so a cached static is safe.
1352            *GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner()) = Some(vt.get_user_data);
1353
1354            unsafe { (vt.set_thread_pool_max)(0) };
1355            let join_fn = vt.join_thread_pool;
1356            std::thread::spawn(move || unsafe { join_fn() });
1357
1358            Ok((
1359                Self {
1360                    _lib: lib,
1361                    vt,
1362                    window,
1363                    cb_binder,
1364                    _wm_class: wm_class,
1365                    register_code,
1366                    unregister_code,
1367                    task_id: -1,
1368                },
1369                consumer,
1370            ))
1371        }
1372
1373        /// Register the callback for `task_id`. If a task was already
1374        /// registered, it is unregistered first (WindowManager tracks one task
1375        /// per callback binder).
1376        pub fn register(&mut self, task_id: i32) -> Result<(), CoreError> {
1377            if self.task_id == task_id {
1378                return Ok(());
1379            }
1380            if self.task_id >= 0 {
1381                let _ = self.unregister();
1382            }
1383
1384            let _ = transact_write(&self.vt, self.window.ptr, self.register_code, |w| {
1385                w.write_i32(task_id)?;
1386                w.write_strong_binder(self.cb_binder)
1387            })?;
1388            self.task_id = task_id;
1389            Ok(())
1390        }
1391
1392        /// Unregister the callback from WindowManager. No-op if nothing is
1393        /// registered.
1394        pub fn unregister(&mut self) -> Result<(), CoreError> {
1395            if self.task_id < 0 {
1396                return Ok(());
1397            }
1398            let _ = transact_write(&self.vt, self.window.ptr, self.unregister_code, |w| {
1399                w.write_strong_binder(self.cb_binder)
1400            })?;
1401            self.task_id = -1;
1402            Ok(())
1403        }
1404
1405        /// The most recent `onFpsReported` value (f32), or `None` if no report
1406        /// has arrived yet. Safe to call at any time; the bit pattern is
1407        /// published atomically. A genuine 0.0 (idle) report reads as `Some`,
1408        /// distinguished from the no-report state by `FPS_SEEN`.
1409        pub fn last_fps(&self) -> Option<f32> {
1410            fps_from_state(
1411                FPS_SEEN.load(Ordering::Acquire),
1412                FPS_VALUE.load(Ordering::Relaxed),
1413            )
1414        }
1415
1416        /// The taskId currently registered, or `None` if none.
1417        pub fn task_id(&self) -> Option<i32> {
1418            (self.task_id >= 0).then_some(self.task_id)
1419        }
1420    }
1421
1422    impl Drop for FpsListener {
1423        /// Best-effort deregistration from WindowManager so a dropped listener
1424        /// does not leave the framework delivering `onFpsReported` forever. The
1425        /// callback binder's local strong ref is intentionally NOT released:
1426        /// keeping it alive guarantees the per-binder userdata (the OwnedFd)
1427        /// can never be reclaimed by `on_destroy` while a callback is in
1428        /// flight, and the framework-side registration has been dropped by the
1429        /// unregister, so no stale transaction targets this instance.
1430        fn drop(&mut self) {
1431            let _ = self.unregister();
1432        }
1433    }
1434
1435    /// Disambiguate "no report yet" from a genuine 0.0 (idle) report: `seen`
1436    /// tracks whether the callback published a value; `bits` is that value's
1437    /// bit pattern. `0.0f32` has bits 0, so the bits alone cannot tell a real
1438    /// idle sample from an unset slot.
1439    fn fps_from_state(seen: bool, bits: u32) -> Option<f32> {
1440        if seen {
1441            Some(f32::from_bits(bits))
1442        } else {
1443            None
1444        }
1445    }
1446
1447    /// Normalize an `onFpsReported` bit pattern for the stream. Real FPS is
1448    /// non-negative and finite; NaN/±Inf (garbage or corruption) and negative
1449    /// values collapse to 0.0 (idle) so the value stream never shows "NaN" or
1450    /// "inf".
1451    fn sanitize_fps(bits: u32) -> u32 {
1452        let v = f32::from_bits(bits);
1453        if v.is_finite() && v >= 0.0 {
1454            bits
1455        } else {
1456            0.0f32.to_bits()
1457        }
1458    }
1459
1460    // ── TaskStackListener (task-stack change wake-up) ────────────────────────
1461
1462    const TASK_SERVICE: &[u8] = b"activity_task\0";
1463    const ATM_DESCRIPTOR: &[u8] = b"android.app.IActivityTaskManager\0";
1464    const TASK_STACK_DESCRIPTOR: &[u8] = b"android.app.ITaskStackListener\0";
1465
1466    // No-op callbacks for the client-only IActivityTaskManager class (we never
1467    // serve transactions on the `activity_task` binder — the class exists only
1468    // to satisfy AIBinder_prepareTransaction's remote-transaction contract).
1469    unsafe extern "C" fn atm_on_create(_: *mut c_void) -> *mut c_void {
1470        std::ptr::null_mut()
1471    }
1472    unsafe extern "C" fn atm_on_destroy(_: *mut c_void) {}
1473    unsafe extern "C" fn atm_on_transact(
1474        _: *mut AIBinder,
1475        _: u32,
1476        _: *const AParcel,
1477        _: *mut AParcel,
1478    ) -> BinderStatus {
1479        STATUS_OK
1480    }
1481
1482    // Pure wake-up handler: any ITaskStackListener callback
1483    // (onTaskStackChanged, onTaskMovedToFront, …) just signals the eventfd.
1484    // The callback arguments are deliberately NOT parsed — the authoritative
1485    // (taskId, pkg) comes from re-querying getFocusedRootTaskInfo (txn 31) on
1486    // the event, so we never depend on a parcel layout (RunningTaskInfo places
1487    // taskId near the parcel tail).
1488    //
1489    // The wake eventfd is per-instance: the daemon hosts two listeners (the fg
1490    // task source and the fps channel), each with its own eventfd. It is handed
1491    // to AIBinder_new as the binder's userdata, so on_destroy must reclaim it.
1492    // No process-wide static — a shared fd would deliver every instance's
1493    // wake to whichever listener opened last.
1494    //
1495    // The callback resolves the per-binder eventfd through AIBinder_getUserData.
1496    // The symbol address is process-wide, so it is cached once in a static.
1497    static GET_USER_DATA: std::sync::Mutex<
1498        Option<unsafe extern "C" fn(*const AIBinder) -> *mut c_void>,
1499    > = std::sync::Mutex::new(None);
1500
1501    unsafe extern "C" fn task_stack_on_create(userdata: *mut c_void) -> *mut c_void {
1502        userdata
1503    }
1504    unsafe extern "C" fn task_stack_on_destroy(userdata: *mut c_void) {
1505        if !userdata.is_null() {
1506            unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
1507        }
1508    }
1509    unsafe extern "C" fn task_stack_on_transact(
1510        binder: *mut AIBinder,
1511        _code: u32,
1512        _in_parcel: *const AParcel,
1513        _reply: *mut AParcel,
1514    ) -> BinderStatus {
1515        let get_user_data = GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner());
1516        if let Some(get_user_data) = *get_user_data {
1517            let userdata = unsafe { get_user_data(binder) };
1518            if !userdata.is_null() {
1519                let efd = userdata as *mut OwnedFd;
1520                let val: u64 = 1;
1521                unsafe { libc::write((*efd).as_raw_fd(), &val as *const u64 as *const c_void, 8) };
1522            }
1523        }
1524        STATUS_OK
1525    }
1526
1527    /// Push-based task-stack change listener registered with
1528    /// `IActivityTaskManager`.
1529    ///
1530    /// Uses `IActivityTaskManager.registerTaskStackListener(listener)`; the
1531    /// daemon hosts the `ITaskStackListener` server object. The callback is a
1532    /// pure wake-up: on any task-stack change it signals the eventfd and
1533    /// parses nothing. Consumers re-query `getFocusedRootTaskInfo` (txn 31) on
1534    /// the event for the authoritative `(taskId, pkg)`.
1535    ///
1536    /// This target's ROM exposes the legacy `ITaskStackListener` /
1537    /// `registerTaskStackListener` pair; the newer `ITaskChangeListener` /
1538    /// `registerTaskChangeListener` interface is absent.
1539    ///
1540    /// The registering UID must hold `MANAGE_ACTIVITY_TASKS` /
1541    /// `MANAGE_ACTIVITY_STACKS` — the same gate as txn 31, which root passes
1542    /// empirically on the target ROM.
1543    ///
1544    /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
1545    /// callback fd. It becomes readable on any task-stack change.
1546    pub struct TaskStackListener {
1547        _lib: DlHandle,
1548        vt: Vtable,
1549        service: OwnedBinder,
1550        cb_binder: *mut AIBinder,
1551        _atm_class: *mut AIBinder_Class,
1552        register_code: u32,
1553        unregister_code: u32,
1554    }
1555    unsafe impl Send for TaskStackListener {}
1556
1557    impl TaskStackListener {
1558        /// Open `activity_task`, define the `ITaskStackListener` server object,
1559        /// and start the binder thread pool. Does **not** register yet — call
1560        /// [`TaskStackListener::register`] once consumers are active.
1561        pub fn open() -> Result<(Self, OwnedFd), CoreError> {
1562            let handle = unsafe {
1563                libc::dlopen(
1564                    LIBBINDER_PATH.as_ptr() as *const c_char,
1565                    libc::RTLD_NOW | libc::RTLD_LOCAL,
1566                )
1567            };
1568            if handle.is_null() {
1569                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
1570            }
1571            let lib = DlHandle;
1572            let vt = load_vtable(handle)?;
1573
1574            let (register_code, unregister_code) = crate::dex::resolve_task_stack_codes()
1575                .ok_or_else(|| {
1576                    CoreError::binder(-1, "dex:TRANSACTION_registerTaskStackListener not found")
1577                })?;
1578
1579            let service = {
1580                let raw = unsafe { (vt.get_service)(TASK_SERVICE.as_ptr() as *const c_char) };
1581                if raw.is_null() {
1582                    return Err(CoreError::binder(
1583                        -1,
1584                        "AServiceManager_getService:activity_task",
1585                    ));
1586                }
1587                OwnedBinder {
1588                    ptr: raw,
1589                    dec_strong: vt.dec_strong,
1590                }
1591            };
1592
1593            // Remote transactions require a class on the binder (same
1594            // AIBinder_prepareTransaction contract as the other services).
1595            let atm_class = unsafe {
1596                (vt.class_define)(
1597                    ATM_DESCRIPTOR.as_ptr() as *const c_char,
1598                    atm_on_create,
1599                    atm_on_destroy,
1600                    atm_on_transact,
1601                )
1602            };
1603            if atm_class.is_null() {
1604                return Err(CoreError::binder(
1605                    -1,
1606                    "AIBinder_Class_define:IActivityTaskManager",
1607                ));
1608            }
1609            unsafe { (vt.associate_class)(service.ptr, atm_class) };
1610
1611            let cb_class = unsafe {
1612                (vt.class_define)(
1613                    TASK_STACK_DESCRIPTOR.as_ptr() as *const c_char,
1614                    task_stack_on_create,
1615                    task_stack_on_destroy,
1616                    task_stack_on_transact,
1617                )
1618            };
1619            if cb_class.is_null() {
1620                return Err(CoreError::binder(
1621                    -1,
1622                    "AIBinder_Class_define:ITaskStackListener",
1623                ));
1624            }
1625
1626            // Blocking eventfd — callback writes, consumer waits/reads. Each
1627            // instance owns its own fd; it is handed to the callback binder as
1628            // userdata (per-instance routing, no process-wide static) and the
1629            // consumer receives a dup below (C2).
1630            let owned = unsafe {
1631                let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
1632                if raw < 0 {
1633                    return Err(CoreError::sys(*libc::__errno(), "eventfd"));
1634                }
1635                OwnedFd::from_raw_fd(raw)
1636            };
1637
1638            let consumer = owned
1639                .try_clone()
1640                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:task_stack"))?;
1641
1642            let userdata = Box::into_raw(Box::new(owned)) as *mut c_void;
1643            let cb_binder = unsafe { (vt.new_binder)(cb_class, userdata) };
1644            if cb_binder.is_null() {
1645                // Reclaim the userdata box handed to AIBinder_new before bailing.
1646                unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
1647                return Err(CoreError::binder(-1, "AIBinder_new:ITaskStackListener"));
1648            }
1649            unsafe { (vt.associate_class)(cb_binder, cb_class) };
1650
1651            // The callback resolves AIBinder_getUserData from this vtable; the
1652            // symbol address is process-wide, so a cached static is safe.
1653            *GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner()) = Some(vt.get_user_data);
1654
1655            unsafe { (vt.set_thread_pool_max)(0) };
1656            let join_fn = vt.join_thread_pool;
1657            std::thread::spawn(move || unsafe { join_fn() });
1658
1659            Ok((
1660                Self {
1661                    _lib: lib,
1662                    vt,
1663                    service,
1664                    cb_binder,
1665                    _atm_class: atm_class,
1666                    register_code,
1667                    unregister_code,
1668                },
1669                consumer,
1670            ))
1671        }
1672
1673        /// Register the task-stack listener with `activity_task` (one listener
1674        /// receives all task-stack events). Idempotent at the framework level;
1675        /// callers should register once and keep the object alive.
1676        pub fn register(&self) -> Result<(), CoreError> {
1677            let _ = transact_write(&self.vt, self.service.ptr, self.register_code, |w| {
1678                w.write_strong_binder(self.cb_binder)
1679            })?;
1680            Ok(())
1681        }
1682
1683        /// Unregister the task-stack listener from `activity_task`. No-op at
1684        /// the framework level if not registered; callers should unregister
1685        /// before dropping the object.
1686        pub fn unregister(&self) -> Result<(), CoreError> {
1687            let _ = transact_write(&self.vt, self.service.ptr, self.unregister_code, |w| {
1688                w.write_strong_binder(self.cb_binder)
1689            })?;
1690            Ok(())
1691        }
1692    }
1693
1694    impl Drop for TaskStackListener {
1695        /// Best-effort deregistration from `activity_task`. Same deliberate
1696        /// non-release of the local strong ref as [`FpsListener`] — the
1697        /// userdata OwnedFd stays valid for any in-flight wake, and the
1698        /// framework-side registration is gone after the unregister.
1699        fn drop(&mut self) {
1700            let _ = self.unregister();
1701        }
1702    }
1703
1704    // ── RawBinderService ──────────────────────────────────────────────────────
1705
1706    /// Generic binder client for any named Android service.
1707    ///
1708    /// Handles its own `dlopen` on `libbinder_ndk.so`. Callers provide raw
1709    /// transaction codes (resolved via [`crate::dex::find_transaction_code`])
1710    /// and use [`RawBinderService::transact_bool`] /
1711    /// [`RawBinderService::transact_i32`] for typed round-trips.
1712    pub struct RawBinderService {
1713        _lib: DlHandle,
1714        vt: Vtable,
1715        service: OwnedBinder,
1716    }
1717    unsafe impl Send for RawBinderService {}
1718
1719    impl RawBinderService {
1720        /// Open a connection to the named service (e.g. `"power"`, `"batterystats"`).
1721        pub fn open(service_name: &str) -> Result<Self, CoreError> {
1722            use std::ffi::CString;
1723            let handle = unsafe {
1724                libc::dlopen(
1725                    LIBBINDER_PATH.as_ptr() as *const c_char,
1726                    libc::RTLD_NOW | libc::RTLD_LOCAL,
1727                )
1728            };
1729            if handle.is_null() {
1730                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
1731            }
1732            let lib = DlHandle;
1733            let vt = load_vtable(handle)?;
1734            let cs = CString::new(service_name)
1735                .map_err(|_| CoreError::binder(-1, "service_name:nul_byte"))?;
1736            let raw = unsafe { (vt.get_service)(cs.as_ptr()) };
1737            if raw.is_null() {
1738                return Err(CoreError::binder(-1, "AServiceManager_getService:null"));
1739            }
1740            let service = OwnedBinder {
1741                ptr: raw,
1742                dec_strong: vt.dec_strong,
1743            };
1744            Ok(Self {
1745                _lib: lib,
1746                vt,
1747                service,
1748            })
1749        }
1750
1751        /// Send a no-argument transaction; read exception header then bool reply.
1752        pub fn transact_bool(&self, code: u32) -> Result<bool, CoreError> {
1753            let out = self.raw_noarg(code)?;
1754            let r = ParcelReader {
1755                vt: &self.vt,
1756                parcel: &out,
1757            };
1758            let ex = r.read_i32()?;
1759            if ex != EX_NONE {
1760                return Err(CoreError::binder(ex, "transact_bool:exception"));
1761            }
1762            if let Some(rb) = self.vt.read_bool {
1763                let mut v = false;
1764                let s = unsafe { rb(out.ptr as *const AParcel, &mut v) };
1765                if s != STATUS_OK {
1766                    return Err(CoreError::binder(s, "AParcel_readBool"));
1767                }
1768                Ok(v)
1769            } else {
1770                Ok(r.read_i32()? != 0)
1771            }
1772        }
1773
1774        /// Send a transaction with one i32 argument; discard reply.
1775        pub fn transact_i32(&self, code: u32, arg: i32) -> Result<(), CoreError> {
1776            let _ = transact_write(&self.vt, self.service.ptr, code, |w| w.write_i32(arg))?;
1777            Ok(())
1778        }
1779
1780        fn raw_noarg(&self, code: u32) -> Result<OwnedParcel, CoreError> {
1781            transact_write(&self.vt, self.service.ptr, code, |_| Ok(()))
1782        }
1783    }
1784
1785    #[cfg(test)]
1786    mod tests {
1787        use super::*;
1788
1789        fn alloc(length: i32) -> bool {
1790            let mut buf = StringBuf::new();
1791            let mut out: *mut c_char = std::ptr::null_mut();
1792            unsafe { string_alloc(&mut buf as *mut StringBuf as *mut c_void, length, &mut out) }
1793        }
1794
1795        #[test]
1796        fn string_alloc_rejects_oversized() {
1797            assert!(!alloc(MAX_BINDER_STRING_LEN as i32 + 1));
1798        }
1799
1800        #[test]
1801        fn string_alloc_rejects_negative() {
1802            assert!(!alloc(-1));
1803        }
1804
1805        #[test]
1806        fn string_alloc_accepts_valid_len_and_nul_terminates() {
1807            let mut buf = StringBuf::new();
1808            let mut out: *mut c_char = std::ptr::null_mut();
1809            let ok =
1810                unsafe { string_alloc(&mut buf as *mut StringBuf as *mut c_void, 4, &mut out) };
1811            assert!(ok);
1812            assert!(!out.is_null());
1813            {
1814                let vec = unsafe { buf.0.as_mut_vec() };
1815                b"ABCD".iter().enumerate().for_each(|(i, &b)| vec[i] = b);
1816                vec[4] = 0;
1817            }
1818            assert_eq!(buf.finish().as_deref(), Some("ABCD"));
1819        }
1820
1821        #[test]
1822        fn fps_zero_report_distinct_from_unseen() {
1823            assert_eq!(fps_from_state(false, 0), None);
1824            assert_eq!(fps_from_state(true, 0), Some(0.0));
1825            assert_eq!(fps_from_state(true, 60.0f32.to_bits()), Some(60.0));
1826            assert_eq!(fps_from_state(false, 60.0f32.to_bits()), None);
1827        }
1828
1829        #[test]
1830        fn sanitize_fps_rejects_non_finite_and_negative() {
1831            let de = |bits| f32::from_bits(sanitize_fps(bits));
1832            assert_eq!(de(0.0f32.to_bits()), 0.0);
1833            assert_eq!(de(60.0f32.to_bits()), 60.0);
1834            assert_eq!(de(f32::NAN.to_bits()), 0.0);
1835            assert_eq!(de(f32::INFINITY.to_bits()), 0.0);
1836            assert_eq!(de(f32::NEG_INFINITY.to_bits()), 0.0);
1837            assert_eq!(de((-5.0f32).to_bits()), 0.0);
1838        }
1839    }
1840}
1841
1842// ── Public re-exports ─────────────────────────────────────────────────────────
1843
1844#[cfg(target_os = "android")]
1845pub use imp::{
1846    ActivityManagerBinder, DisplayManagerBinder, FpsListener, RawBinderService, TaskStackListener,
1847    TxCodes, last_foreground_pid, resolve_tx_codes,
1848};
1849
1850// ── Non-Android stubs ─────────────────────────────────────────────────────────
1851
1852#[cfg(not(target_os = "android"))]
1853pub struct ActivityManagerBinder;
1854
1855#[cfg(not(target_os = "android"))]
1856impl ActivityManagerBinder {
1857    pub fn open() -> Result<Self, crate::CoreError> {
1858        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1859    }
1860    pub fn open_with_observer() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1861        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1862    }
1863    pub fn open_with_fgproc_observer() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1864        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1865    }
1866    pub fn get_focused_task(&self) -> Result<Option<(i32, Option<String>)>, crate::CoreError> {
1867        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1868    }
1869    pub fn get_focused_package(&self) -> Result<Option<String>, crate::CoreError> {
1870        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1871    }
1872    pub fn get_focused_task_id(&self) -> Result<Option<i32>, crate::CoreError> {
1873        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1874    }
1875}
1876
1877#[cfg(not(target_os = "android"))]
1878pub struct DisplayManagerBinder;
1879
1880#[cfg(not(target_os = "android"))]
1881impl DisplayManagerBinder {
1882    pub fn open_with_callback() -> Result<(Self, crate::reactor::Fd), crate::CoreError> {
1883        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1884    }
1885    pub fn is_interactive(&self) -> Result<bool, crate::CoreError> {
1886        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1887    }
1888}
1889
1890#[cfg(not(target_os = "android"))]
1891pub struct RawBinderService;
1892
1893#[cfg(not(target_os = "android"))]
1894impl RawBinderService {
1895    pub fn open(_service_name: &str) -> Result<Self, crate::CoreError> {
1896        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1897    }
1898    pub fn transact_bool(&self, _code: u32) -> Result<bool, crate::CoreError> {
1899        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1900    }
1901    pub fn transact_i32(&self, _code: u32, _arg: i32) -> Result<(), crate::CoreError> {
1902        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1903    }
1904}
1905
1906#[cfg(not(target_os = "android"))]
1907pub struct FpsListener;
1908
1909#[cfg(not(target_os = "android"))]
1910impl FpsListener {
1911    pub fn open() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1912        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1913    }
1914    pub fn register(&mut self, _task_id: i32) -> Result<(), crate::CoreError> {
1915        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1916    }
1917    pub fn unregister(&mut self) -> Result<(), crate::CoreError> {
1918        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1919    }
1920    pub fn last_fps(&self) -> Option<f32> {
1921        None
1922    }
1923    pub fn task_id(&self) -> Option<i32> {
1924        None
1925    }
1926}
1927
1928#[cfg(not(target_os = "android"))]
1929pub struct TaskStackListener;
1930
1931#[cfg(not(target_os = "android"))]
1932impl TaskStackListener {
1933    pub fn open() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1934        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1935    }
1936    pub fn register(&self) -> Result<(), crate::CoreError> {
1937        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1938    }
1939    pub fn unregister(&self) -> Result<(), crate::CoreError> {
1940        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1941    }
1942}
1943
1944#[cfg(not(target_os = "android"))]
1945pub struct TxCodes {
1946    pub observer_code: u32,
1947    pub query_code: u32,
1948    pub api_mode: u8,
1949    pub fg_code: u32,
1950}
1951
1952#[cfg(not(target_os = "android"))]
1953pub fn resolve_tx_codes() -> Result<TxCodes, crate::CoreError> {
1954    Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1955}
1956
1957#[cfg(not(target_os = "android"))]
1958pub fn last_foreground_pid() -> i32 {
1959    0
1960}