Skip to main content

coreshift_core/binder/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/
4
5//! NDK binder primitives for querying Android system services.
6//!
7//! Uses `dlopen` on `libbinder_ndk.so` to avoid hard-linking against a library
8//! absent from older NDK toolchains or non-Android targets.
9//!
10//! ## Transaction code resolution
11//!
12//! Transaction codes are resolved fresh from `framework.jar` DEX on each
13//! startup; no persistent cache.
14//!
15//! ## Observer mode
16//!
17//! `ActivityManagerBinder::open_with_observer` registers this process as an
18//! `IProcessObserver` with ActivityManager. Callbacks fire when foreground
19//! activities change and signal an `eventfd` that callers can poll via epoll.
20//! After the eventfd fires, call `get_focused_package` to read the new value.
21
22// ─────────────────────────────────────────────────────────────────────────────
23// Android-only implementation
24// ─────────────────────────────────────────────────────────────────────────────
25
26#[cfg(target_os = "android")]
27mod imp {
28    use crate::CoreError;
29    use crate::dex;
30    use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd};
31    use std::os::raw::{c_char, c_void};
32    use std::sync::atomic::{AtomicI32, AtomicU32, AtomicUsize, Ordering};
33    use std::sync::Mutex;
34
35    // ── NDK binder status codes ───────────────────────────────────────────────
36
37    const STATUS_OK: i32 = 0;
38    const STATUS_UNKNOWN_TRANSACTION: i32 = -2;
39    const EX_NONE: i32 = 0;
40
41    // ── Interface constants ───────────────────────────────────────────────────
42
43    const AM_DESCRIPTOR:    &[u8] = b"android.app.IActivityManager\0";
44    const OBS_DESCRIPTOR:   &[u8] = b"android.app.IProcessObserver\0";
45    const FGPROC_DESCRIPTOR: &[u8] = b"android.app.IForegroundProcessObserver\0";
46    const ACTIVITY_SERVICE: &[u8] = b"activity\0";
47    #[cfg(target_pointer_width = "64")]
48    const LIBBINDER_PATH: &[u8] = b"/system/lib64/libbinder_ndk.so\0";
49    #[cfg(target_pointer_width = "32")]
50    const LIBBINDER_PATH: &[u8] = b"/system/lib/libbinder_ndk.so\0";
51
52    // ── Tx code cache ─────────────────────────────────────────────────────────
53    // Format (watcher.c compatible): observer_code query_code api_mode fg_code
54    // api_mode: 1 = getFocusedRootTaskInfo, 2 = getFocusedStackInfo (API 29)
55
56    // ── Statics for observer callback (binder thread pool context) ────────────
57    // The core owns the eventfd for the observer lifetime; the consumer receives
58    // a dup and may close it freely. The callback only ever writes to the core's
59    // copy, so it can never touch a closed/recycled fd (C2). The mutex guards
60    // publication/revocation against a callback firing concurrently.
61
62    static OBS_FG_CODE: AtomicU32 = AtomicU32::new(0);
63    static OBS_EVENTFD:  Mutex<Option<OwnedFd>> = Mutex::new(None);
64
65    fn obs_eventfd_guard() -> std::sync::MutexGuard<'static, Option<OwnedFd>> {
66        OBS_EVENTFD.lock().unwrap_or_else(|p| p.into_inner())
67    }
68
69    // ── IForegroundProcessObserver statics ───────────────────────────────────
70    // Separate from the IProcessObserver pair; same C2 discipline (core owns
71    // the eventfd, consumer gets a dup). FGPROC_READ_I32 holds the vtable's
72    // AParcel_readInt32 fn pointer so the callback can decode the `int pid`
73    // argument without owning the Vtable; it is published (non-zero) before
74    // FGPROC_FG_CODE/eventfd, so the callback never races an unset reader.
75
76    static FGPROC_FG_CODE: AtomicU32 = AtomicU32::new(0);
77    static FGPROC_PID: AtomicI32 = AtomicI32::new(0);
78    static FGPROC_EVENTFD: Mutex<Option<OwnedFd>> = Mutex::new(None);
79    static FGPROC_READ_I32: AtomicUsize = AtomicUsize::new(0);
80
81    fn fgproc_eventfd_guard() -> std::sync::MutexGuard<'static, Option<OwnedFd>> {
82        FGPROC_EVENTFD.lock().unwrap_or_else(|p| p.into_inner())
83    }
84
85    // ── Raw NDK type aliases ──────────────────────────────────────────────────
86
87    type AIBinder = c_void;
88    #[allow(non_camel_case_types)]
89    type AIBinder_Class = c_void;
90    type AParcel = c_void;
91    type BinderStatus = i32;
92    type StringAllocator = unsafe extern "C" fn(*mut c_void, i32, *mut *mut c_char) -> bool;
93
94    // ── AIBinder_Class callbacks ──────────────────────────────────────────────
95
96    // AM client — no-op server side (we're a client only)
97    unsafe extern "C" fn am_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
98    unsafe extern "C" fn am_on_destroy(_: *mut c_void) {}
99    unsafe extern "C" fn am_on_transact(
100        _: *mut AIBinder, _: u32, _: *const AParcel, _: *mut AParcel,
101    ) -> BinderStatus { STATUS_UNKNOWN_TRANSACTION }
102
103    // IProcessObserver server callbacks
104    unsafe extern "C" fn obs_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
105    unsafe extern "C" fn obs_on_destroy(_: *mut c_void) {}
106    unsafe extern "C" fn obs_on_transact(
107        _: *mut AIBinder, code: u32, _: *const AParcel, _: *mut AParcel,
108    ) -> BinderStatus {
109        if code == OBS_FG_CODE.load(Ordering::Relaxed) {
110            // Write while holding the lock: the fd can only be closed while
111            // we hold it, so a revoke can never race us into a stale number.
112            if let Some(fd) = obs_eventfd_guard().as_ref() {
113                let val: u64 = 1;
114                unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
115            }
116        }
117        STATUS_OK
118    }
119
120    // IForegroundProcessObserver server callbacks. The authoritative
121    // foreground PID arrives as the `int pid` argument in the in-parcel; it is
122    // decoded and stored so callers can read it after the eventfd fires.
123    unsafe extern "C" fn fgproc_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
124    unsafe extern "C" fn fgproc_on_destroy(_: *mut c_void) {}
125    unsafe extern "C" fn fgproc_on_transact(
126        _: *mut AIBinder, code: u32, in_parcel: *const AParcel, _: *mut AParcel,
127    ) -> BinderStatus {
128        if code != FGPROC_FG_CODE.load(Ordering::Relaxed) {
129            return STATUS_UNKNOWN_TRANSACTION;
130        }
131        // Read fn is published non-zero before the code/eventfd, so a matching
132        // code is never paired with an unset reader.
133        let read_addr = FGPROC_READ_I32.load(Ordering::Relaxed);
134        if read_addr != 0 {
135            let read_fn: unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus =
136                unsafe { std::mem::transmute(read_addr) };
137            let mut pid: i32 = 0;
138            if unsafe { read_fn(in_parcel, &mut pid) } == STATUS_OK {
139                FGPROC_PID.store(pid, Ordering::Relaxed);
140            }
141        }
142        // Write while holding the lock: the fd can only be closed while we
143        // hold it, so a revoke can never race us into a stale number.
144        if let Some(fd) = fgproc_eventfd_guard().as_ref() {
145            let val: u64 = 1;
146            unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
147        }
148        STATUS_OK
149    }
150
151    /// The PID captured by the most recent `onForegroundProcessChanged`
152    /// callback (requires `open_with_fgproc_observer`).
153    pub fn last_foreground_pid() -> i32 {
154        FGPROC_PID.load(Ordering::Relaxed)
155    }
156
157    // ── String allocator ─────────────────────────────────────────────────────
158
159    unsafe extern "C" fn string_alloc(
160        cookie: *mut c_void, length: i32, buffer: *mut *mut c_char,
161    ) -> bool {
162        if length < 0 { return true; }
163        let s = unsafe { &mut *(cookie as *mut StringBuf) };
164        s.0.reserve_exact(length as usize + 1);
165        unsafe { s.0.as_mut_vec().resize(length as usize + 1, 0) };
166        unsafe { *buffer = s.0.as_mut_ptr() as *mut c_char };
167        true
168    }
169
170    struct StringBuf(String);
171    impl StringBuf {
172        fn new() -> Self { Self(String::new()) }
173        fn finish(mut self) -> Option<String> {
174            if let Some(pos) = self.0.as_bytes().iter().position(|&b| b == 0) {
175                unsafe { self.0.as_mut_vec().truncate(pos) };
176            }
177            if self.0.is_empty() { None } else { Some(self.0) }
178        }
179    }
180
181    // ── Vtable ────────────────────────────────────────────────────────────────
182
183    struct Vtable {
184        get_service:         unsafe extern "C" fn(*const c_char) -> *mut AIBinder,
185        class_define:        unsafe extern "C" fn(
186                                 *const c_char,
187                                 unsafe extern "C" fn(*mut c_void) -> *mut c_void,
188                                 unsafe extern "C" fn(*mut c_void),
189                                 unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel) -> BinderStatus,
190                             ) -> *mut AIBinder_Class,
191        associate_class:     unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool,
192        new_binder:          unsafe extern "C" fn(*const AIBinder_Class, *mut c_void) -> *mut AIBinder,
193        prepare_transaction: unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus,
194        transact:            unsafe extern "C" fn(*mut AIBinder, u32, *mut *mut AParcel, *mut *mut AParcel, u32) -> BinderStatus,
195        dec_strong:          unsafe extern "C" fn(*mut AIBinder),
196        parcel_delete:       unsafe extern "C" fn(*mut AParcel),
197        read_int32:          unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus,
198        read_string:         unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus,
199        write_strong_binder: unsafe extern "C" fn(*mut AParcel, *mut AIBinder) -> BinderStatus,
200        set_thread_pool_max: unsafe extern "C" fn(u32),
201        join_thread_pool:    unsafe extern "C" fn(),
202        write_int32:         unsafe extern "C" fn(*mut AParcel, i32) -> BinderStatus,
203        // Optional: only present on API 29+, but all modern Android has this
204        read_bool:           Option<unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus>,
205    }
206
207    // ── RAII wrappers ─────────────────────────────────────────────────────────
208
209    struct DlHandle(*mut c_void);
210    unsafe impl Send for DlHandle {}
211    impl Drop for DlHandle {
212        fn drop(&mut self) {
213            // Intentionally no dlclose: the binder thread pool spawned in
214            // open_with_observer() keeps executing library code until process
215            // exit. Unloading the library while that thread runs causes
216            // use-after-free. libbinder_ndk.so is never unloaded during the
217            // daemon lifetime; the OS reclaims it on exit.
218        }
219    }
220
221    struct OwnedParcel { ptr: *mut AParcel, delete: unsafe extern "C" fn(*mut AParcel) }
222    impl Drop for OwnedParcel {
223        fn drop(&mut self) { if !self.ptr.is_null() { unsafe { (self.delete)(self.ptr) }; } }
224    }
225
226    struct OwnedBinder { ptr: *mut AIBinder, dec_strong: unsafe extern "C" fn(*mut AIBinder) }
227    unsafe impl Send for OwnedBinder {}
228    impl Drop for OwnedBinder {
229        fn drop(&mut self) { if !self.ptr.is_null() { unsafe { (self.dec_strong)(self.ptr) }; } }
230    }
231
232    // ── dlsym helper ─────────────────────────────────────────────────────────
233
234    macro_rules! dlsym_fn {
235        ($handle:expr, $name:literal, $ty:ty) => {{
236            let sym = unsafe {
237                libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char)
238            };
239            if sym.is_null() {
240                return Err(CoreError::binder(-1, concat!("dlsym:", $name)));
241            }
242            unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }
243        }};
244    }
245
246    macro_rules! dlsym_opt {
247        ($handle:expr, $name:literal, $ty:ty) => {{
248            let sym = unsafe {
249                libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char)
250            };
251            if sym.is_null() { None }
252            else { Some(unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }) }
253        }};
254    }
255
256    fn load_vtable(handle: *mut c_void) -> Result<Vtable, CoreError> {
257        Ok(Vtable {
258            get_service: dlsym_fn!(handle, "AServiceManager_getService",
259                unsafe extern "C" fn(*const c_char) -> *mut AIBinder),
260            class_define: dlsym_fn!(handle, "AIBinder_Class_define",
261                unsafe extern "C" fn(
262                    *const c_char,
263                    unsafe extern "C" fn(*mut c_void) -> *mut c_void,
264                    unsafe extern "C" fn(*mut c_void),
265                    unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel) -> BinderStatus,
266                ) -> *mut AIBinder_Class),
267            associate_class: dlsym_fn!(handle, "AIBinder_associateClass",
268                unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool),
269            new_binder: dlsym_fn!(handle, "AIBinder_new",
270                unsafe extern "C" fn(*const AIBinder_Class, *mut c_void) -> *mut AIBinder),
271            prepare_transaction: dlsym_fn!(handle, "AIBinder_prepareTransaction",
272                unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus),
273            transact: dlsym_fn!(handle, "AIBinder_transact",
274                unsafe extern "C" fn(*mut AIBinder, u32, *mut *mut AParcel, *mut *mut AParcel, u32) -> BinderStatus),
275            dec_strong: dlsym_fn!(handle, "AIBinder_decStrong",
276                unsafe extern "C" fn(*mut AIBinder)),
277            parcel_delete: dlsym_fn!(handle, "AParcel_delete",
278                unsafe extern "C" fn(*mut AParcel)),
279            read_int32: dlsym_fn!(handle, "AParcel_readInt32",
280                unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus),
281            read_string: dlsym_fn!(handle, "AParcel_readString",
282                unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus),
283            write_strong_binder: dlsym_fn!(handle, "AParcel_writeStrongBinder",
284                unsafe extern "C" fn(*mut AParcel, *mut AIBinder) -> BinderStatus),
285            set_thread_pool_max: dlsym_fn!(handle, "ABinderProcess_setThreadPoolMaxThreadCount",
286                unsafe extern "C" fn(u32)),
287            join_thread_pool: dlsym_fn!(handle, "ABinderProcess_joinThreadPool",
288                unsafe extern "C" fn()),
289            write_int32: dlsym_fn!(handle, "AParcel_writeInt32",
290                unsafe extern "C" fn(*mut AParcel, i32) -> BinderStatus),
291            read_bool: dlsym_opt!(handle, "AParcel_readBool",
292                unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus),
293        })
294    }
295
296    // ── ParcelReader ──────────────────────────────────────────────────────────
297
298    struct ParcelReader<'a> { vt: &'a Vtable, parcel: &'a OwnedParcel }
299
300    impl<'a> ParcelReader<'a> {
301        fn read_i32(&self) -> Result<i32, CoreError> {
302            let mut v = 0i32;
303            let s = unsafe { (self.vt.read_int32)(self.parcel.ptr, &mut v) };
304            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readInt32")); }
305            Ok(v)
306        }
307        fn read_string(&self) -> Result<Option<String>, CoreError> {
308            let mut buf = StringBuf::new();
309            let s = unsafe {
310                (self.vt.read_string)(self.parcel.ptr, &mut buf as *mut StringBuf as *mut c_void, string_alloc)
311            };
312            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readString")); }
313            Ok(buf.finish())
314        }
315        fn skip_i32s(&self, n: usize) -> Result<(), CoreError> {
316            for _ in 0..n { self.read_i32()?; }
317            Ok(())
318        }
319        fn skip_int_array(&self) -> Result<(), CoreError> {
320            let count = self.read_i32()?.max(0) as usize;
321            self.skip_i32s(count)
322        }
323        fn read_first_package_from_names(&self) -> Result<Option<String>, CoreError> {
324            let count = self.read_i32()?.max(0) as usize;
325            let mut first: Option<String> = None;
326            for _ in 0..count {
327                let s = self.read_string()?;
328                if first.is_none() {
329                    first = s.and_then(|c| c.split('/').next().map(str::to_owned));
330                }
331            }
332            Ok(first)
333        }
334    }
335
336    // ── Response parsers ──────────────────────────────────────────────────────
337
338    fn parse_root_task_info_body(r: &ParcelReader<'_>) -> Result<Option<String>, CoreError> {
339        let scratch = r.read_i32()?;
340        if scratch != 0 { r.skip_i32s(4)?; }
341        r.skip_int_array()?;
342        r.read_first_package_from_names()
343    }
344
345    fn parse_stack_info_body(r: &ParcelReader<'_>) -> Result<Option<String>, CoreError> {
346        r.skip_i32s(5)?;
347        r.skip_int_array()?;
348        r.read_first_package_from_names()
349    }
350
351    // ── Tx code resolution ────────────────────────────────────────────────────
352
353    pub struct TxCodes {
354        pub observer_code: u32,
355        pub query_code:    u32,
356        pub api_mode:      u8,  // 1 = RootTaskInfo, 2 = StackInfo
357        pub fg_code:       u32,
358    }
359
360    pub fn resolve_tx_codes() -> Result<TxCodes, CoreError> {
361        let (obs, query, api, fg) = dex::resolve_tx_codes_from_dex()
362            .ok_or_else(|| CoreError::binder(-1, "tx_code_resolution:dex_parse_failed"))?;
363        Ok(TxCodes { observer_code: obs, query_code: query, api_mode: api, fg_code: fg })
364    }
365
366    // ── ActivityManagerBinder ─────────────────────────────────────────────────
367
368    pub struct ActivityManagerBinder {
369        _lib:    DlHandle,
370        vt:      Vtable,
371        _class:  *mut AIBinder_Class,
372        service: OwnedBinder,
373        tx_code: u32,
374        legacy:  bool,
375    }
376    unsafe impl Send for ActivityManagerBinder {}
377
378    impl ActivityManagerBinder {
379        fn open_inner(handle: *mut c_void) -> Result<(DlHandle, Vtable, *mut AIBinder_Class, OwnedBinder), CoreError> {
380            let lib = DlHandle(handle);
381            let vt = load_vtable(handle)?;
382
383            let am_class = unsafe {
384                (vt.class_define)(
385                    AM_DESCRIPTOR.as_ptr() as *const c_char,
386                    am_on_create, am_on_destroy, am_on_transact,
387                )
388            };
389            if am_class.is_null() { return Err(CoreError::binder(-1, "AIBinder_Class_define:AM")); }
390
391            let raw = unsafe { (vt.get_service)(ACTIVITY_SERVICE.as_ptr() as *const c_char) };
392            if raw.is_null() { return Err(CoreError::binder(-1, "AServiceManager_getService:activity")); }
393            unsafe { (vt.associate_class)(raw, am_class) };
394
395            let service = OwnedBinder { ptr: raw, dec_strong: vt.dec_strong };
396            Ok((lib, vt, am_class, service))
397        }
398
399        fn dlopen_libbinder() -> Result<*mut c_void, CoreError> {
400            use std::os::raw::c_char;
401            let handle = unsafe {
402                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
403            };
404            if handle.is_null() { return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so")); }
405            Ok(handle)
406        }
407
408        /// Open ActivityManager binder (polling mode — no observer).
409        /// Resolves the query tx code from cache or DEX.
410        pub fn open() -> Result<Self, CoreError> {
411            let handle = Self::dlopen_libbinder()?;
412            let (lib, vt, class, service) = Self::open_inner(handle)?;
413            let codes = resolve_tx_codes()?;
414            let legacy = codes.api_mode == 2;
415            Ok(Self { _lib: lib, vt, _class: class, service, tx_code: codes.query_code, legacy })
416        }
417
418        /// Open ActivityManager binder and register as IProcessObserver.
419        ///
420        /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
421        /// callback fd. It becomes readable whenever `onForegroundActivitiesChanged`
422        /// fires. Caller must add it to epoll and may close it at any time — the
423        /// callback keeps writing to the core's copy, so closing the returned
424        /// fd never invalidates the notification path (C2). After the event
425        /// fires, call `get_focused_package`.
426        pub fn open_with_observer() -> Result<(Self, OwnedFd), CoreError> {
427            let handle = Self::dlopen_libbinder()?;
428            let (lib, vt, am_class, service) = Self::open_inner(handle)?;
429            let codes = resolve_tx_codes()?;
430            let legacy = codes.api_mode == 2;
431
432            // Create eventfd for callback → epoll bridge. Ownership stays in the
433            // core for the observer lifetime; the consumer receives a dup below.
434            let owned = unsafe {
435                let raw = libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC);
436                if raw < 0 { return Err(CoreError::sys(*libc::__errno(), "eventfd")); }
437                OwnedFd::from_raw_fd(raw)
438            };
439
440            // Define IProcessObserver class (we're the server)
441            let obs_class = unsafe {
442                (vt.class_define)(
443                    OBS_DESCRIPTOR.as_ptr() as *const c_char,
444                    obs_on_create, obs_on_destroy, obs_on_transact,
445                )
446            };
447            if obs_class.is_null() {
448                return Err(CoreError::binder(-1, "AIBinder_Class_define:Observer"));
449            }
450
451            // Instantiate our observer binder object
452            let obs_binder = unsafe { (vt.new_binder)(obs_class, std::ptr::null_mut()) };
453            if obs_binder.is_null() {
454                return Err(CoreError::binder(-1, "AIBinder_new:Observer"));
455            }
456            unsafe { (vt.associate_class)(obs_binder, obs_class) };
457
458            // Call registerProcessObserver(observer)
459            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
460            let s = unsafe { (vt.prepare_transaction)(service.ptr, &mut in_ptr) };
461            if s != STATUS_OK {
462                return Err(CoreError::binder(s, "prepareTransaction:registerObserver"));
463            }
464            unsafe { (vt.write_strong_binder)(in_ptr, obs_binder) };
465            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
466            let s = unsafe {
467                (vt.transact)(service.ptr, codes.observer_code, &mut in_ptr, &mut out_ptr, 0)
468            };
469            if !out_ptr.is_null() { unsafe { (vt.parcel_delete)(out_ptr) }; }
470            if s != STATUS_OK {
471                return Err(CoreError::binder(s, "transact:registerProcessObserver"));
472            }
473
474            // Consumer dup — made before publishing, so an error path drops the
475            // owned fd without ever leaving a stale handle for the callback.
476            let consumer = owned.try_clone()
477                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:observer"))?;
478
479            // Publish fg_code and the core-owned eventfd for the callback
480            OBS_FG_CODE.store(codes.fg_code, Ordering::Relaxed);
481            *obs_eventfd_guard() = Some(owned);
482
483            // Start binder thread pool — blocks forever in background thread
484            unsafe { (vt.set_thread_pool_max)(0) };
485            let join_fn = vt.join_thread_pool;
486            std::thread::spawn(move || unsafe { join_fn() });
487
488            let binder = Self { _lib: lib, vt, _class: am_class, service, tx_code: codes.query_code, legacy };
489            Ok((binder, consumer))
490        }
491
492        /// Open ActivityManager binder and register as IForegroundProcessObserver.
493        ///
494        /// The authoritative foreground PID is delivered in
495        /// `onForegroundProcessChanged` callbacks; this is the low-noise,
496        /// post-Android-14 foreground source. The callback stores the PID
497        /// (readable via [`last_foreground_pid`]) and signals the returned
498        /// eventfd.
499        ///
500        /// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
501        /// callback fd. It becomes readable whenever
502        /// `onForegroundProcessChanged` fires. Same lifetime contract as
503        /// [`ActivityManagerBinder::open_with_observer`] (C2): the core owns
504        /// the eventfd and the callback only ever writes to that copy, so
505        /// closing the returned dup never invalidates the notification path.
506        pub fn open_with_fgproc_observer() -> Result<(Self, OwnedFd), CoreError> {
507            let handle = Self::dlopen_libbinder()?;
508            let (lib, vt, am_class, service) = Self::open_inner(handle)?;
509            let (register_code, fgproc_code) = crate::dex::resolve_fgproc_codes()
510                .ok_or_else(|| CoreError::binder(-1, "tx_code_resolution:fgproc_dex_parse_failed"))?;
511
512            // Create eventfd for callback → epoll bridge. Ownership stays in the
513            // core for the observer lifetime; the consumer receives a dup below.
514            let owned = unsafe {
515                let raw = libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC);
516                if raw < 0 { return Err(CoreError::sys(*libc::__errno(), "eventfd")); }
517                OwnedFd::from_raw_fd(raw)
518            };
519
520            // Define IForegroundProcessObserver class (we're the server)
521            let obs_class = unsafe {
522                (vt.class_define)(
523                    FGPROC_DESCRIPTOR.as_ptr() as *const c_char,
524                    fgproc_on_create, fgproc_on_destroy, fgproc_on_transact,
525                )
526            };
527            if obs_class.is_null() {
528                return Err(CoreError::binder(-1, "AIBinder_Class_define:FGProcessObserver"));
529            }
530
531            // Instantiate our observer binder object
532            let obs_binder = unsafe { (vt.new_binder)(obs_class, std::ptr::null_mut()) };
533            if obs_binder.is_null() {
534                return Err(CoreError::binder(-1, "AIBinder_new:FGProcessObserver"));
535            }
536            unsafe { (vt.associate_class)(obs_binder, obs_class) };
537
538            // Call registerForegroundProcessObserver(observer)
539            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
540            let s = unsafe { (vt.prepare_transaction)(service.ptr, &mut in_ptr) };
541            if s != STATUS_OK {
542                return Err(CoreError::binder(s, "prepareTransaction:registerForegroundProcessObserver"));
543            }
544            unsafe { (vt.write_strong_binder)(in_ptr, obs_binder) };
545            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
546            let s = unsafe {
547                (vt.transact)(service.ptr, register_code, &mut in_ptr, &mut out_ptr, 0)
548            };
549            if !out_ptr.is_null() { unsafe { (vt.parcel_delete)(out_ptr) }; }
550            if s != STATUS_OK {
551                return Err(CoreError::binder(s, "transact:registerForegroundProcessObserver"));
552            }
553
554            // Consumer dup — made before publishing, so an error path drops the
555            // owned fd without ever leaving a stale handle for the callback.
556            let consumer = owned.try_clone()
557                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:fgproc_observer"))?;
558
559            // Publish reader fn, fg code, pid base, and the core-owned eventfd
560            // for the callback. Reader is published first so the callback never
561            // sees a matching code with an unset reader (C2-adjacent init order).
562            FGPROC_READ_I32.store(vt.read_int32 as usize, Ordering::Relaxed);
563            FGPROC_FG_CODE.store(fgproc_code, Ordering::Relaxed);
564            FGPROC_PID.store(0, Ordering::Relaxed);
565            *fgproc_eventfd_guard() = Some(owned);
566
567            // Start binder thread pool — blocks forever in background thread
568            unsafe { (vt.set_thread_pool_max)(0) };
569            let join_fn = vt.join_thread_pool;
570            std::thread::spawn(move || unsafe { join_fn() });
571
572            let binder = Self { _lib: lib, vt, _class: am_class, service, tx_code: 0, legacy: false };
573            Ok((binder, consumer))
574        }
575
576        fn do_transact(&self) -> Result<OwnedParcel, CoreError> {
577            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
578            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut in_ptr) };
579            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
580            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
581            let s = unsafe {
582                (self.vt.transact)(self.service.ptr, self.tx_code, &mut in_ptr, &mut out_ptr, 0)
583            };
584            let out = OwnedParcel { ptr: out_ptr, delete: self.vt.parcel_delete };
585            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
586            Ok(out)
587        }
588
589        pub fn get_focused_package(&self) -> Result<Option<String>, CoreError> {
590            let out = self.do_transact()?;
591            let r = ParcelReader { vt: &self.vt, parcel: &out };
592            let ex = r.read_i32()?;
593            if ex != EX_NONE { return Err(CoreError::binder(ex, "getFocusedTask:exception")); }
594            let present = r.read_i32()?;
595            if present == 0 { return Ok(None); }
596            if self.legacy { parse_stack_info_body(&r) } else { parse_root_task_info_body(&r) }
597        }
598    }
599
600    // ── DisplayManagerBinder ─────────────────────────────────────────────────
601
602    const DISPLAY_SERVICE:    &[u8] = b"display\0";
603    const DISPLAY_DESCRIPTOR: &[u8] = b"android.hardware.display.IDisplayManager\0";
604    const CALLBACK_DESCRIPTOR: &[u8] = b"android.hardware.display.IDisplayManagerCallback\0";
605    const POWER_SERVICE:      &[u8] = b"power\0";
606
607    const TX_DISPLAY_REGISTER_CALLBACK: u32 = 4;
608
609    // Core owns the callback eventfd; the consumer gets a dup and may close it
610    // freely. Same lifetime discipline as the ActivityManager observer (C2).
611    static DISP_EVENTFD: Mutex<Option<OwnedFd>> = Mutex::new(None);
612
613    fn disp_eventfd_guard() -> std::sync::MutexGuard<'static, Option<OwnedFd>> {
614        DISP_EVENTFD.lock().unwrap_or_else(|p| p.into_inner())
615    }
616
617    unsafe extern "C" fn disp_cb_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
618    unsafe extern "C" fn disp_cb_on_destroy(_: *mut c_void) {}
619    unsafe extern "C" fn disp_cb_on_transact(
620        _: *mut AIBinder, code: u32, _: *const AParcel, _: *mut AParcel,
621    ) -> BinderStatus {
622        if code == 1 {
623            if let Some(fd) = disp_eventfd_guard().as_ref() {
624                let val: u64 = 1;
625                unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
626            }
627        }
628        STATUS_OK
629    }
630
631    pub struct DisplayManagerBinder {
632        _lib:           DlHandle,
633        vt:             Vtable,
634        display:        OwnedBinder,
635        power:          Option<OwnedBinder>,
636        is_interactive_tx: u32,
637    }
638    unsafe impl Send for DisplayManagerBinder {}
639
640    impl DisplayManagerBinder {
641        pub fn open_with_callback() -> Result<(Self, crate::reactor::Fd), CoreError> {
642            let handle = unsafe {
643                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
644            };
645            if handle.is_null() { return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so")); }
646            let lib = DlHandle(handle);
647            let vt = load_vtable(handle)?;
648
649            // Blocking eventfd (no EFD_NONBLOCK) — callback writes, caller's
650            // read_u64_blocking() waits. The core owns it for the callback's
651            // lifetime; the consumer receives a dup below (C2).
652            let owned = unsafe {
653                let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
654                if raw < 0 { return Err(CoreError::sys(*libc::__errno(), "eventfd")); }
655                OwnedFd::from_raw_fd(raw)
656            };
657
658            // Get display service (no class_define needed for client-only)
659            let raw_display = unsafe { (vt.get_service)(DISPLAY_SERVICE.as_ptr() as *const c_char) };
660            if raw_display.is_null() {
661                return Err(CoreError::binder(-1, "AServiceManager_getService:display"));
662            }
663            let display = OwnedBinder { ptr: raw_display, dec_strong: vt.dec_strong };
664
665            // Define IDisplayManagerCallback (we're the server receiving callbacks)
666            let cb_class = unsafe {
667                (vt.class_define)(
668                    CALLBACK_DESCRIPTOR.as_ptr() as *const c_char,
669                    disp_cb_on_create, disp_cb_on_destroy, disp_cb_on_transact,
670                )
671            };
672            if cb_class.is_null() {
673                return Err(CoreError::binder(-1, "AIBinder_Class_define:DisplayCallback"));
674            }
675
676            let cb_binder = unsafe { (vt.new_binder)(cb_class, std::ptr::null_mut()) };
677            if cb_binder.is_null() {
678                return Err(CoreError::binder(-1, "AIBinder_new:DisplayCallback"));
679            }
680
681            // registerCallback(callback) — tx 4
682            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
683            let s = unsafe { (vt.prepare_transaction)(display.ptr, &mut in_ptr) };
684            if s != STATUS_OK {
685                return Err(CoreError::binder(s, "prepareTransaction:registerCallback"));
686            }
687            unsafe { (vt.write_strong_binder)(in_ptr, cb_binder) };
688            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
689            let s = unsafe {
690                (vt.transact)(display.ptr, TX_DISPLAY_REGISTER_CALLBACK, &mut in_ptr, &mut out_ptr, 0)
691            };
692            if !out_ptr.is_null() { unsafe { (vt.parcel_delete)(out_ptr) }; }
693            if s != STATUS_OK {
694                return Err(CoreError::binder(s, "transact:registerCallback"));
695            }
696
697            // Optional: grab power service for is_interactive()
698            let power = {
699                let raw = unsafe { (vt.get_service)(POWER_SERVICE.as_ptr() as *const c_char) };
700                if raw.is_null() { None } else { Some(OwnedBinder { ptr: raw, dec_strong: vt.dec_strong }) }
701            };
702
703            // Resolve isInteractive tx code from DEX at open time
704            let is_interactive_tx = crate::dex::resolve_is_interactive_tx()
705                .ok_or_else(|| CoreError::binder(-1, "dex:TRANSACTION_isInteractive not found"))?;
706
707            // Consumer dup — made before publishing, so an error path drops the
708            // owned fd without ever leaving a stale handle for the callback.
709            let efd_owned = owned.try_clone()
710                .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:display"))
711                .and_then(|dup| unsafe {
712                    crate::reactor::Fd::from_owned_raw_fd(dup.into_raw_fd(), "display.efd")
713                        .map_err(|_| CoreError::binder(-1, "Fd::from_owned_raw_fd:display.efd"))
714                })?;
715
716            // Publish the core-owned eventfd for the callback
717            *disp_eventfd_guard() = Some(owned);
718
719            // Join binder thread pool so callbacks can fire
720            unsafe { (vt.set_thread_pool_max)(0) };
721            let join_fn = vt.join_thread_pool;
722            std::thread::spawn(move || unsafe { join_fn() });
723
724            Ok((Self { _lib: lib, vt, display, power, is_interactive_tx }, efd_owned))
725        }
726
727        pub fn is_interactive(&self) -> Result<bool, CoreError> {
728            let power = self.power.as_ref()
729                .ok_or_else(|| CoreError::binder(-1, "power:unavailable"))?;
730            let mut inp: *mut AParcel = std::ptr::null_mut();
731            let s = unsafe { (self.vt.prepare_transaction)(power.ptr, &mut inp) };
732            if s != STATUS_OK { return Err(CoreError::binder(s, "prepareTransaction:isInteractive")); }
733            let mut out: *mut AParcel = std::ptr::null_mut();
734            let s = unsafe {
735                (self.vt.transact)(power.ptr, self.is_interactive_tx, &mut inp, &mut out, 0)
736            };
737            let out = OwnedParcel { ptr: out, delete: self.vt.parcel_delete };
738            if s != STATUS_OK { return Err(CoreError::binder(s, "transact:isInteractive")); }
739            let r = ParcelReader { vt: &self.vt, parcel: &out };
740            let ex = r.read_i32()?;
741            if ex != EX_NONE { return Err(CoreError::binder(ex, "isInteractive:exception")); }
742            if let Some(rb) = self.vt.read_bool {
743                let mut v = false;
744                let s = unsafe { rb(out.ptr as *const AParcel, &mut v) };
745                if s != STATUS_OK { return Err(CoreError::binder(s, "readBool:isInteractive")); }
746                Ok(v)
747            } else {
748                Ok(r.read_i32()? != 0)
749            }
750        }
751    }
752
753    // ── RawBinderService ──────────────────────────────────────────────────────
754
755    /// Generic binder client for any named Android service.
756    ///
757    /// Handles its own `dlopen` on `libbinder_ndk.so`. Callers provide raw
758    /// transaction codes (resolved via [`crate::dex::find_transaction_code`])
759    /// and use [`RawBinderService::transact_bool`] /
760    /// [`RawBinderService::transact_i32`] for typed round-trips.
761    pub struct RawBinderService {
762        _lib:    DlHandle,
763        vt:      Vtable,
764        service: OwnedBinder,
765    }
766    unsafe impl Send for RawBinderService {}
767
768    impl RawBinderService {
769        /// Open a connection to the named service (e.g. `"power"`, `"batterystats"`).
770        pub fn open(service_name: &str) -> Result<Self, CoreError> {
771            use std::ffi::CString;
772            let handle = unsafe {
773                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
774            };
775            if handle.is_null() {
776                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
777            }
778            let lib = DlHandle(handle);
779            let vt = load_vtable(handle)?;
780            let cs = CString::new(service_name)
781                .map_err(|_| CoreError::binder(-1, "service_name:nul_byte"))?;
782            let raw = unsafe { (vt.get_service)(cs.as_ptr()) };
783            if raw.is_null() {
784                return Err(CoreError::binder(-1, "AServiceManager_getService:null"));
785            }
786            let service = OwnedBinder { ptr: raw, dec_strong: vt.dec_strong };
787            Ok(Self { _lib: lib, vt, service })
788        }
789
790        /// Send a no-argument transaction; read exception header then bool reply.
791        pub fn transact_bool(&self, code: u32) -> Result<bool, CoreError> {
792            let out = self.raw_noarg(code)?;
793            let r = ParcelReader { vt: &self.vt, parcel: &out };
794            let ex = r.read_i32()?;
795            if ex != EX_NONE { return Err(CoreError::binder(ex, "transact_bool:exception")); }
796            if let Some(rb) = self.vt.read_bool {
797                let mut v = false;
798                let s = unsafe { rb(out.ptr as *const AParcel, &mut v) };
799                if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readBool")); }
800                Ok(v)
801            } else {
802                Ok(r.read_i32()? != 0)
803            }
804        }
805
806        /// Send a transaction with one i32 argument; discard reply.
807        pub fn transact_i32(&self, code: u32, arg: i32) -> Result<(), CoreError> {
808            let mut inp: *mut AParcel = std::ptr::null_mut();
809            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut inp) };
810            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
811            let s = unsafe { (self.vt.write_int32)(inp, arg) };
812            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_writeInt32")); }
813            let mut out: *mut AParcel = std::ptr::null_mut();
814            let s = unsafe { (self.vt.transact)(self.service.ptr, code, &mut inp, &mut out, 0) };
815            if !out.is_null() { unsafe { (self.vt.parcel_delete)(out) }; }
816            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
817            Ok(())
818        }
819
820        fn raw_noarg(&self, code: u32) -> Result<OwnedParcel, CoreError> {
821            let mut inp: *mut AParcel = std::ptr::null_mut();
822            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut inp) };
823            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
824            let mut out: *mut AParcel = std::ptr::null_mut();
825            let s = unsafe { (self.vt.transact)(self.service.ptr, code, &mut inp, &mut out, 0) };
826            let out = OwnedParcel { ptr: out, delete: self.vt.parcel_delete };
827            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
828            Ok(out)
829        }
830    }
831}
832
833// ── Public re-exports ─────────────────────────────────────────────────────────
834
835#[cfg(target_os = "android")]
836pub use imp::{ActivityManagerBinder, DisplayManagerBinder, RawBinderService, TxCodes, last_foreground_pid, resolve_tx_codes};
837
838// ── Non-Android stubs ─────────────────────────────────────────────────────────
839
840#[cfg(not(target_os = "android"))]
841pub struct ActivityManagerBinder;
842
843#[cfg(not(target_os = "android"))]
844impl ActivityManagerBinder {
845    pub fn open() -> Result<Self, crate::CoreError> {
846        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
847    }
848    pub fn open_with_observer() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
849        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
850    }
851    pub fn open_with_fgproc_observer() -> Result<(Self, std::os::fd::OwnedFd), crate::CoreError> {
852        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
853    }
854    pub fn get_focused_package(&self) -> Result<Option<String>, crate::CoreError> {
855        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
856    }
857}
858
859#[cfg(not(target_os = "android"))]
860pub struct DisplayManagerBinder;
861
862#[cfg(not(target_os = "android"))]
863impl DisplayManagerBinder {
864    pub fn open_with_callback() -> Result<(Self, crate::reactor::Fd), crate::CoreError> {
865        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
866    }
867    pub fn is_interactive(&self) -> Result<bool, crate::CoreError> {
868        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
869    }
870}
871
872#[cfg(not(target_os = "android"))]
873pub struct RawBinderService;
874
875#[cfg(not(target_os = "android"))]
876impl RawBinderService {
877    pub fn open(_service_name: &str) -> Result<Self, crate::CoreError> {
878        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
879    }
880    pub fn transact_bool(&self, _code: u32) -> Result<bool, crate::CoreError> {
881        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
882    }
883    pub fn transact_i32(&self, _code: u32, _arg: i32) -> Result<(), crate::CoreError> {
884        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
885    }
886}
887
888#[cfg(not(target_os = "android"))]
889pub struct TxCodes { pub observer_code: u32, pub query_code: u32, pub api_mode: u8, pub fg_code: u32 }
890
891#[cfg(not(target_os = "android"))]
892pub fn resolve_tx_codes() -> Result<TxCodes, crate::CoreError> {
893    Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
894}
895
896#[cfg(not(target_os = "android"))]
897pub fn last_foreground_pid() -> i32 {
898    0
899}