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            // Blocking eventfd — callback writes, consumer waits/reads. Each
1317            // instance owns its own fd; it is handed to the callback binder as
1318            // userdata (per-instance routing, no process-wide static) and the
1319            // consumer receives a dup below (C2).
1320            let owned = unsafe {
1321                let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
1322                if raw < 0 {
1323                    return Err(CoreError::sys(*libc::__errno(), "eventfd"));
1324                }
1325                OwnedFd::from_raw_fd(raw)
1326            };
1327
1328            let consumer = owned
1329                .try_clone()
1330                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:fps"))?;
1331
1332            let userdata = Box::into_raw(Box::new(owned)) as *mut c_void;
1333            let cb_binder = unsafe { (vt.new_binder)(cb_class, userdata) };
1334            if cb_binder.is_null() {
1335                // Reclaim the userdata box handed to AIBinder_new before bailing.
1336                unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
1337                return Err(CoreError::binder(-1, "AIBinder_new:ITaskFpsCallback"));
1338            }
1339            unsafe { (vt.associate_class)(cb_binder, cb_class) };
1340
1341            // Publish the reader and code before the eventfd registration; a
1342            // matching code is never paired with an unset reader (C2-adjacent).
1343            FPS_READ_I32.store(vt.read_int32 as usize, Ordering::Relaxed);
1344            FPS_SEEN.store(false, Ordering::Relaxed);
1345            FPS_CODE.store(on_fps_code, Ordering::Relaxed);
1346            FPS_VALUE.store(0, Ordering::Relaxed);
1347            // The callback resolves AIBinder_getUserData from this vtable; the
1348            // symbol address is process-wide, so a cached static is safe.
1349            *GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner()) = Some(vt.get_user_data);
1350
1351            unsafe { (vt.set_thread_pool_max)(0) };
1352            let join_fn = vt.join_thread_pool;
1353            std::thread::spawn(move || unsafe { join_fn() });
1354
1355            Ok((
1356                Self {
1357                    _lib: lib,
1358                    vt,
1359                    window,
1360                    cb_binder,
1361                    _wm_class: wm_class,
1362                    register_code,
1363                    unregister_code,
1364                    task_id: -1,
1365                },
1366                consumer,
1367            ))
1368        }
1369
1370        /// Register the callback for `task_id`. If a task was already
1371        /// registered, it is unregistered first (WindowManager tracks one task
1372        /// per callback binder).
1373        pub fn register(&mut self, task_id: i32) -> Result<(), CoreError> {
1374            if self.task_id == task_id {
1375                return Ok(());
1376            }
1377            if self.task_id >= 0 {
1378                let _ = self.unregister();
1379            }
1380
1381            let _ = transact_write(&self.vt, self.window.ptr, self.register_code, |w| {
1382                w.write_i32(task_id)?;
1383                w.write_strong_binder(self.cb_binder)
1384            })?;
1385            self.task_id = task_id;
1386            Ok(())
1387        }
1388
1389        /// Unregister the callback from WindowManager. No-op if nothing is
1390        /// registered.
1391        pub fn unregister(&mut self) -> Result<(), CoreError> {
1392            if self.task_id < 0 {
1393                return Ok(());
1394            }
1395            let _ = transact_write(&self.vt, self.window.ptr, self.unregister_code, |w| {
1396                w.write_strong_binder(self.cb_binder)
1397            })?;
1398            self.task_id = -1;
1399            Ok(())
1400        }
1401
1402        /// The most recent `onFpsReported` value (f32), or `None` if no report
1403        /// has arrived yet. Safe to call at any time; the bit pattern is
1404        /// published atomically. A genuine 0.0 (idle) report reads as `Some`,
1405        /// distinguished from the no-report state by `FPS_SEEN`.
1406        pub fn last_fps(&self) -> Option<f32> {
1407            fps_from_state(
1408                FPS_SEEN.load(Ordering::Acquire),
1409                FPS_VALUE.load(Ordering::Relaxed),
1410            )
1411        }
1412
1413        /// The taskId currently registered, or `None` if none.
1414        pub fn task_id(&self) -> Option<i32> {
1415            (self.task_id >= 0).then_some(self.task_id)
1416        }
1417    }
1418
1419    impl Drop for FpsListener {
1420        /// Best-effort deregistration from WindowManager so a dropped listener
1421        /// does not leave the framework delivering `onFpsReported` forever. The
1422        /// callback binder's local strong ref is intentionally NOT released:
1423        /// keeping it alive guarantees the per-binder userdata (the OwnedFd)
1424        /// can never be reclaimed by `on_destroy` while a callback is in
1425        /// flight, and the framework-side registration has been dropped by the
1426        /// unregister, so no stale transaction targets this instance.
1427        fn drop(&mut self) {
1428            let _ = self.unregister();
1429        }
1430    }
1431
1432    /// Disambiguate "no report yet" from a genuine 0.0 (idle) report: `seen`
1433    /// tracks whether the callback published a value; `bits` is that value's
1434    /// bit pattern. `0.0f32` has bits 0, so the bits alone cannot tell a real
1435    /// idle sample from an unset slot.
1436    fn fps_from_state(seen: bool, bits: u32) -> Option<f32> {
1437        if seen {
1438            Some(f32::from_bits(bits))
1439        } else {
1440            None
1441        }
1442    }
1443
1444    /// Normalize an `onFpsReported` bit pattern for the stream. Real FPS is
1445    /// non-negative and finite; NaN/±Inf (garbage or corruption) and negative
1446    /// values collapse to 0.0 (idle) so the value stream never shows "NaN" or
1447    /// "inf".
1448    fn sanitize_fps(bits: u32) -> u32 {
1449        let v = f32::from_bits(bits);
1450        if v.is_finite() && v >= 0.0 {
1451            bits
1452        } else {
1453            0.0f32.to_bits()
1454        }
1455    }
1456
1457    // ── TaskStackListener (task-stack change wake-up) ────────────────────────
1458
1459    const TASK_SERVICE: &[u8] = b"activity_task\0";
1460    const ATM_DESCRIPTOR: &[u8] = b"android.app.IActivityTaskManager\0";
1461    const TASK_STACK_DESCRIPTOR: &[u8] = b"android.app.ITaskStackListener\0";
1462
1463    // No-op callbacks for the client-only IActivityTaskManager class (we never
1464    // serve transactions on the `activity_task` binder — the class exists only
1465    // to satisfy AIBinder_prepareTransaction's remote-transaction contract).
1466    unsafe extern "C" fn atm_on_create(_: *mut c_void) -> *mut c_void {
1467        std::ptr::null_mut()
1468    }
1469    unsafe extern "C" fn atm_on_destroy(_: *mut c_void) {}
1470    unsafe extern "C" fn atm_on_transact(
1471        _: *mut AIBinder,
1472        _: u32,
1473        _: *const AParcel,
1474        _: *mut AParcel,
1475    ) -> BinderStatus {
1476        STATUS_OK
1477    }
1478
1479    // Pure wake-up handler: any ITaskStackListener callback
1480    // (onTaskStackChanged, onTaskMovedToFront, …) just signals the eventfd.
1481    // The callback arguments are deliberately NOT parsed — the authoritative
1482    // (taskId, pkg) comes from re-querying getFocusedRootTaskInfo (txn 31) on
1483    // the event, so we never depend on a parcel layout (RunningTaskInfo places
1484    // taskId near the parcel tail).
1485    //
1486    // The wake eventfd is per-instance: the daemon hosts two listeners (the fg
1487    // task source and the fps channel), each with its own eventfd. It is handed
1488    // to AIBinder_new as the binder's userdata, so on_destroy must reclaim it.
1489    // No process-wide static — a shared fd would deliver every instance's
1490    // wake to whichever listener opened last.
1491    //
1492    // The callback resolves the per-binder eventfd through AIBinder_getUserData.
1493    // The symbol address is process-wide, so it is cached once in a static.
1494    static GET_USER_DATA: std::sync::Mutex<
1495        Option<unsafe extern "C" fn(*const AIBinder) -> *mut c_void>,
1496    > = std::sync::Mutex::new(None);
1497
1498    unsafe extern "C" fn task_stack_on_create(userdata: *mut c_void) -> *mut c_void {
1499        userdata
1500    }
1501    unsafe extern "C" fn task_stack_on_destroy(userdata: *mut c_void) {
1502        if !userdata.is_null() {
1503            unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
1504        }
1505    }
1506    unsafe extern "C" fn task_stack_on_transact(
1507        binder: *mut AIBinder,
1508        _code: u32,
1509        _in_parcel: *const AParcel,
1510        _reply: *mut AParcel,
1511    ) -> BinderStatus {
1512        let get_user_data = GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner());
1513        if let Some(get_user_data) = *get_user_data {
1514            let userdata = unsafe { get_user_data(binder) };
1515            if !userdata.is_null() {
1516                let efd = userdata as *mut OwnedFd;
1517                let val: u64 = 1;
1518                unsafe { libc::write((*efd).as_raw_fd(), &val as *const u64 as *const c_void, 8) };
1519            }
1520        }
1521        STATUS_OK
1522    }
1523
1524    /// Push-based task-stack change listener registered with
1525    /// `IActivityTaskManager`.
1526    ///
1527    /// Uses `IActivityTaskManager.registerTaskStackListener(listener)`; the
1528    /// daemon hosts the `ITaskStackListener` server object. The callback is a
1529    /// pure wake-up: on any task-stack change it signals the eventfd and
1530    /// parses nothing. Consumers re-query `getFocusedRootTaskInfo` (txn 31) on
1531    /// the event for the authoritative `(taskId, pkg)`.
1532    ///
1533    /// This target's ROM exposes the legacy `ITaskStackListener` /
1534    /// `registerTaskStackListener` pair; the newer `ITaskChangeListener` /
1535    /// `registerTaskChangeListener` interface is absent.
1536    ///
1537    /// The registering UID must hold `MANAGE_ACTIVITY_TASKS` /
1538    /// `MANAGE_ACTIVITY_STACKS` — the same gate as txn 31, which root passes
1539    /// empirically on the target ROM.
1540    ///
1541    /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
1542    /// callback fd. It becomes readable on any task-stack change.
1543    pub struct TaskStackListener {
1544        _lib: DlHandle,
1545        vt: Vtable,
1546        service: OwnedBinder,
1547        cb_binder: *mut AIBinder,
1548        _atm_class: *mut AIBinder_Class,
1549        register_code: u32,
1550        unregister_code: u32,
1551    }
1552    unsafe impl Send for TaskStackListener {}
1553
1554    impl TaskStackListener {
1555        /// Open `activity_task`, define the `ITaskStackListener` server object,
1556        /// and start the binder thread pool. Does **not** register yet — call
1557        /// [`TaskStackListener::register`] once consumers are active.
1558        pub fn open() -> Result<(Self, OwnedFd), CoreError> {
1559            let handle = unsafe {
1560                libc::dlopen(
1561                    LIBBINDER_PATH.as_ptr() as *const c_char,
1562                    libc::RTLD_NOW | libc::RTLD_LOCAL,
1563                )
1564            };
1565            if handle.is_null() {
1566                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
1567            }
1568            let lib = DlHandle;
1569            let vt = load_vtable(handle)?;
1570
1571            let (register_code, unregister_code) = crate::dex::resolve_task_stack_codes()
1572                .ok_or_else(|| {
1573                    CoreError::binder(-1, "dex:TRANSACTION_registerTaskStackListener not found")
1574                })?;
1575
1576            let service = {
1577                let raw = unsafe { (vt.get_service)(TASK_SERVICE.as_ptr() as *const c_char) };
1578                if raw.is_null() {
1579                    return Err(CoreError::binder(
1580                        -1,
1581                        "AServiceManager_getService:activity_task",
1582                    ));
1583                }
1584                OwnedBinder {
1585                    ptr: raw,
1586                    dec_strong: vt.dec_strong,
1587                }
1588            };
1589
1590            // Remote transactions require a class on the binder (same
1591            // AIBinder_prepareTransaction contract as the other services).
1592            let atm_class = unsafe {
1593                (vt.class_define)(
1594                    ATM_DESCRIPTOR.as_ptr() as *const c_char,
1595                    atm_on_create,
1596                    atm_on_destroy,
1597                    atm_on_transact,
1598                )
1599            };
1600            if atm_class.is_null() {
1601                return Err(CoreError::binder(
1602                    -1,
1603                    "AIBinder_Class_define:IActivityTaskManager",
1604                ));
1605            }
1606            unsafe { (vt.associate_class)(service.ptr, atm_class) };
1607
1608            let cb_class = unsafe {
1609                (vt.class_define)(
1610                    TASK_STACK_DESCRIPTOR.as_ptr() as *const c_char,
1611                    task_stack_on_create,
1612                    task_stack_on_destroy,
1613                    task_stack_on_transact,
1614                )
1615            };
1616            if cb_class.is_null() {
1617                return Err(CoreError::binder(
1618                    -1,
1619                    "AIBinder_Class_define:ITaskStackListener",
1620                ));
1621            }
1622
1623            // Blocking eventfd — callback writes, consumer waits/reads. Each
1624            // instance owns its own fd; it is handed to the callback binder as
1625            // userdata (per-instance routing, no process-wide static) and the
1626            // consumer receives a dup below (C2).
1627            let owned = unsafe {
1628                let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
1629                if raw < 0 {
1630                    return Err(CoreError::sys(*libc::__errno(), "eventfd"));
1631                }
1632                OwnedFd::from_raw_fd(raw)
1633            };
1634
1635            let consumer = owned
1636                .try_clone()
1637                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:task_stack"))?;
1638
1639            let userdata = Box::into_raw(Box::new(owned)) as *mut c_void;
1640            let cb_binder = unsafe { (vt.new_binder)(cb_class, userdata) };
1641            if cb_binder.is_null() {
1642                // Reclaim the userdata box handed to AIBinder_new before bailing.
1643                unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
1644                return Err(CoreError::binder(-1, "AIBinder_new:ITaskStackListener"));
1645            }
1646            unsafe { (vt.associate_class)(cb_binder, cb_class) };
1647
1648            // The callback resolves AIBinder_getUserData from this vtable; the
1649            // symbol address is process-wide, so a cached static is safe.
1650            *GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner()) = Some(vt.get_user_data);
1651
1652            unsafe { (vt.set_thread_pool_max)(0) };
1653            let join_fn = vt.join_thread_pool;
1654            std::thread::spawn(move || unsafe { join_fn() });
1655
1656            Ok((
1657                Self {
1658                    _lib: lib,
1659                    vt,
1660                    service,
1661                    cb_binder,
1662                    _atm_class: atm_class,
1663                    register_code,
1664                    unregister_code,
1665                },
1666                consumer,
1667            ))
1668        }
1669
1670        /// Register the task-stack listener with `activity_task` (one listener
1671        /// receives all task-stack events). Idempotent at the framework level;
1672        /// callers should register once and keep the object alive.
1673        pub fn register(&self) -> Result<(), CoreError> {
1674            let _ = transact_write(&self.vt, self.service.ptr, self.register_code, |w| {
1675                w.write_strong_binder(self.cb_binder)
1676            })?;
1677            Ok(())
1678        }
1679
1680        /// Unregister the task-stack listener from `activity_task`. No-op at
1681        /// the framework level if not registered; callers should unregister
1682        /// before dropping the object.
1683        pub fn unregister(&self) -> Result<(), CoreError> {
1684            let _ = transact_write(&self.vt, self.service.ptr, self.unregister_code, |w| {
1685                w.write_strong_binder(self.cb_binder)
1686            })?;
1687            Ok(())
1688        }
1689    }
1690
1691    impl Drop for TaskStackListener {
1692        /// Best-effort deregistration from `activity_task`. Same deliberate
1693        /// non-release of the local strong ref as [`FpsListener`] — the
1694        /// userdata OwnedFd stays valid for any in-flight wake, and the
1695        /// framework-side registration is gone after the unregister.
1696        fn drop(&mut self) {
1697            let _ = self.unregister();
1698        }
1699    }
1700
1701    // ── RawBinderService ──────────────────────────────────────────────────────
1702
1703    /// Generic binder client for any named Android service.
1704    ///
1705    /// Handles its own `dlopen` on `libbinder_ndk.so`. Callers provide raw
1706    /// transaction codes (resolved via [`crate::dex::find_transaction_code`])
1707    /// and use [`RawBinderService::transact_bool`] /
1708    /// [`RawBinderService::transact_i32`] for typed round-trips.
1709    pub struct RawBinderService {
1710        _lib: DlHandle,
1711        vt: Vtable,
1712        service: OwnedBinder,
1713    }
1714    unsafe impl Send for RawBinderService {}
1715
1716    impl RawBinderService {
1717        /// Open a connection to the named service (e.g. `"power"`, `"batterystats"`).
1718        pub fn open(service_name: &str) -> Result<Self, CoreError> {
1719            use std::ffi::CString;
1720            let handle = unsafe {
1721                libc::dlopen(
1722                    LIBBINDER_PATH.as_ptr() as *const c_char,
1723                    libc::RTLD_NOW | libc::RTLD_LOCAL,
1724                )
1725            };
1726            if handle.is_null() {
1727                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
1728            }
1729            let lib = DlHandle;
1730            let vt = load_vtable(handle)?;
1731            let cs = CString::new(service_name)
1732                .map_err(|_| CoreError::binder(-1, "service_name:nul_byte"))?;
1733            let raw = unsafe { (vt.get_service)(cs.as_ptr()) };
1734            if raw.is_null() {
1735                return Err(CoreError::binder(-1, "AServiceManager_getService:null"));
1736            }
1737            let service = OwnedBinder {
1738                ptr: raw,
1739                dec_strong: vt.dec_strong,
1740            };
1741            Ok(Self {
1742                _lib: lib,
1743                vt,
1744                service,
1745            })
1746        }
1747
1748        /// Send a no-argument transaction; read exception header then bool reply.
1749        pub fn transact_bool(&self, code: u32) -> Result<bool, CoreError> {
1750            let out = self.raw_noarg(code)?;
1751            let r = ParcelReader {
1752                vt: &self.vt,
1753                parcel: &out,
1754            };
1755            let ex = r.read_i32()?;
1756            if ex != EX_NONE {
1757                return Err(CoreError::binder(ex, "transact_bool:exception"));
1758            }
1759            if let Some(rb) = self.vt.read_bool {
1760                let mut v = false;
1761                let s = unsafe { rb(out.ptr as *const AParcel, &mut v) };
1762                if s != STATUS_OK {
1763                    return Err(CoreError::binder(s, "AParcel_readBool"));
1764                }
1765                Ok(v)
1766            } else {
1767                Ok(r.read_i32()? != 0)
1768            }
1769        }
1770
1771        /// Send a transaction with one i32 argument; discard reply.
1772        pub fn transact_i32(&self, code: u32, arg: i32) -> Result<(), CoreError> {
1773            let _ = transact_write(&self.vt, self.service.ptr, code, |w| w.write_i32(arg))?;
1774            Ok(())
1775        }
1776
1777        fn raw_noarg(&self, code: u32) -> Result<OwnedParcel, CoreError> {
1778            transact_write(&self.vt, self.service.ptr, code, |_| Ok(()))
1779        }
1780    }
1781
1782    #[cfg(test)]
1783    mod tests {
1784        use super::*;
1785
1786        fn alloc(length: i32) -> bool {
1787            let mut buf = StringBuf::new();
1788            let mut out: *mut c_char = std::ptr::null_mut();
1789            unsafe { string_alloc(&mut buf as *mut StringBuf as *mut c_void, length, &mut out) }
1790        }
1791
1792        #[test]
1793        fn string_alloc_rejects_oversized() {
1794            assert!(!alloc(MAX_BINDER_STRING_LEN as i32 + 1));
1795        }
1796
1797        #[test]
1798        fn string_alloc_rejects_negative() {
1799            assert!(!alloc(-1));
1800        }
1801
1802        #[test]
1803        fn string_alloc_accepts_valid_len_and_nul_terminates() {
1804            let mut buf = StringBuf::new();
1805            let mut out: *mut c_char = std::ptr::null_mut();
1806            let ok =
1807                unsafe { string_alloc(&mut buf as *mut StringBuf as *mut c_void, 4, &mut out) };
1808            assert!(ok);
1809            assert!(!out.is_null());
1810            {
1811                let vec = unsafe { buf.0.as_mut_vec() };
1812                b"ABCD".iter().enumerate().for_each(|(i, &b)| vec[i] = b);
1813                vec[4] = 0;
1814            }
1815            assert_eq!(buf.finish().as_deref(), Some("ABCD"));
1816        }
1817
1818        #[test]
1819        fn fps_zero_report_distinct_from_unseen() {
1820            assert_eq!(fps_from_state(false, 0), None);
1821            assert_eq!(fps_from_state(true, 0), Some(0.0));
1822            assert_eq!(fps_from_state(true, 60.0f32.to_bits()), Some(60.0));
1823            assert_eq!(fps_from_state(false, 60.0f32.to_bits()), None);
1824        }
1825
1826        #[test]
1827        fn sanitize_fps_rejects_non_finite_and_negative() {
1828            let de = |bits| f32::from_bits(sanitize_fps(bits));
1829            assert_eq!(de(0.0f32.to_bits()), 0.0);
1830            assert_eq!(de(60.0f32.to_bits()), 60.0);
1831            assert_eq!(de(f32::NAN.to_bits()), 0.0);
1832            assert_eq!(de(f32::INFINITY.to_bits()), 0.0);
1833            assert_eq!(de(f32::NEG_INFINITY.to_bits()), 0.0);
1834            assert_eq!(de((-5.0f32).to_bits()), 0.0);
1835        }
1836    }
1837}
1838
1839// ── Public re-exports ─────────────────────────────────────────────────────────
1840
1841#[cfg(target_os = "android")]
1842pub use imp::{
1843    ActivityManagerBinder, DisplayManagerBinder, FpsListener, RawBinderService, TaskStackListener,
1844    TxCodes, last_foreground_pid, resolve_tx_codes,
1845};
1846
1847// ── Non-Android stubs ─────────────────────────────────────────────────────────
1848
1849#[cfg(not(target_os = "android"))]
1850pub struct ActivityManagerBinder;
1851
1852#[cfg(not(target_os = "android"))]
1853impl ActivityManagerBinder {
1854    pub fn open() -> Result<Self, crate::CoreError> {
1855        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1856    }
1857    pub fn open_with_observer() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1858        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1859    }
1860    pub fn open_with_fgproc_observer() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1861        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1862    }
1863    pub fn get_focused_task(&self) -> Result<Option<(i32, Option<String>)>, crate::CoreError> {
1864        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1865    }
1866    pub fn get_focused_package(&self) -> Result<Option<String>, crate::CoreError> {
1867        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1868    }
1869    pub fn get_focused_task_id(&self) -> Result<Option<i32>, crate::CoreError> {
1870        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1871    }
1872}
1873
1874#[cfg(not(target_os = "android"))]
1875pub struct DisplayManagerBinder;
1876
1877#[cfg(not(target_os = "android"))]
1878impl DisplayManagerBinder {
1879    pub fn open_with_callback() -> Result<(Self, crate::reactor::Fd), crate::CoreError> {
1880        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1881    }
1882    pub fn is_interactive(&self) -> Result<bool, crate::CoreError> {
1883        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1884    }
1885}
1886
1887#[cfg(not(target_os = "android"))]
1888pub struct RawBinderService;
1889
1890#[cfg(not(target_os = "android"))]
1891impl RawBinderService {
1892    pub fn open(_service_name: &str) -> Result<Self, crate::CoreError> {
1893        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1894    }
1895    pub fn transact_bool(&self, _code: u32) -> Result<bool, crate::CoreError> {
1896        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1897    }
1898    pub fn transact_i32(&self, _code: u32, _arg: i32) -> Result<(), crate::CoreError> {
1899        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1900    }
1901}
1902
1903#[cfg(not(target_os = "android"))]
1904pub struct FpsListener;
1905
1906#[cfg(not(target_os = "android"))]
1907impl FpsListener {
1908    pub fn open() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1909        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1910    }
1911    pub fn register(&mut self, _task_id: i32) -> Result<(), crate::CoreError> {
1912        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1913    }
1914    pub fn unregister(&mut self) -> Result<(), crate::CoreError> {
1915        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1916    }
1917    pub fn last_fps(&self) -> Option<f32> {
1918        None
1919    }
1920    pub fn task_id(&self) -> Option<i32> {
1921        None
1922    }
1923}
1924
1925#[cfg(not(target_os = "android"))]
1926pub struct TaskStackListener;
1927
1928#[cfg(not(target_os = "android"))]
1929impl TaskStackListener {
1930    pub fn open() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
1931        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1932    }
1933    pub fn register(&self) -> Result<(), crate::CoreError> {
1934        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1935    }
1936    pub fn unregister(&self) -> Result<(), crate::CoreError> {
1937        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1938    }
1939}
1940
1941#[cfg(not(target_os = "android"))]
1942pub struct TxCodes {
1943    pub observer_code: u32,
1944    pub query_code: u32,
1945    pub api_mode: u8,
1946    pub fg_code: u32,
1947}
1948
1949#[cfg(not(target_os = "android"))]
1950pub fn resolve_tx_codes() -> Result<TxCodes, crate::CoreError> {
1951    Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
1952}
1953
1954#[cfg(not(target_os = "android"))]
1955pub fn last_foreground_pid() -> i32 {
1956    0
1957}