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