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::raw::{c_char, c_void};
31    use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
32
33    // ── NDK binder status codes ───────────────────────────────────────────────
34
35    const STATUS_OK: i32 = 0;
36    const STATUS_UNKNOWN_TRANSACTION: i32 = -2;
37    const EX_NONE: i32 = 0;
38
39    // ── Interface constants ───────────────────────────────────────────────────
40
41    const AM_DESCRIPTOR:    &[u8] = b"android.app.IActivityManager\0";
42    const OBS_DESCRIPTOR:   &[u8] = b"android.app.IProcessObserver\0";
43    const ACTIVITY_SERVICE: &[u8] = b"activity\0";
44    #[cfg(target_pointer_width = "64")]
45    const LIBBINDER_PATH: &[u8] = b"/system/lib64/libbinder_ndk.so\0";
46    #[cfg(target_pointer_width = "32")]
47    const LIBBINDER_PATH: &[u8] = b"/system/lib/libbinder_ndk.so\0";
48
49    // ── Tx code cache ─────────────────────────────────────────────────────────
50    // Format (watcher.c compatible): observer_code query_code api_mode fg_code
51    // api_mode: 1 = getFocusedRootTaskInfo, 2 = getFocusedStackInfo (API 29)
52
53    // ── Statics for observer callback (binder thread pool context) ────────────
54    // These are set once during observer setup before joinThreadPool, and then
55    // only read from the callback. Safe to access from multiple binder threads.
56
57    static OBS_FG_CODE: AtomicU32 = AtomicU32::new(0);
58    static OBS_EVENTFD:  AtomicI32 = AtomicI32::new(-1);
59
60    // ── Raw NDK type aliases ──────────────────────────────────────────────────
61
62    type AIBinder = c_void;
63    #[allow(non_camel_case_types)]
64    type AIBinder_Class = c_void;
65    type AParcel = c_void;
66    type BinderStatus = i32;
67    type StringAllocator = unsafe extern "C" fn(*mut c_void, i32, *mut *mut c_char) -> bool;
68
69    // ── AIBinder_Class callbacks ──────────────────────────────────────────────
70
71    // AM client — no-op server side (we're a client only)
72    unsafe extern "C" fn am_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
73    unsafe extern "C" fn am_on_destroy(_: *mut c_void) {}
74    unsafe extern "C" fn am_on_transact(
75        _: *mut AIBinder, _: u32, _: *const AParcel, _: *mut AParcel,
76    ) -> BinderStatus { STATUS_UNKNOWN_TRANSACTION }
77
78    // IProcessObserver server callbacks
79    unsafe extern "C" fn obs_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
80    unsafe extern "C" fn obs_on_destroy(_: *mut c_void) {}
81    unsafe extern "C" fn obs_on_transact(
82        _: *mut AIBinder, code: u32, _: *const AParcel, _: *mut AParcel,
83    ) -> BinderStatus {
84        if code == OBS_FG_CODE.load(Ordering::Relaxed) {
85            let efd = OBS_EVENTFD.load(Ordering::Relaxed);
86            if efd >= 0 {
87                let val: u64 = 1;
88                unsafe { libc::write(efd, &val as *const u64 as *const c_void, 8) };
89            }
90        }
91        STATUS_OK
92    }
93
94    // ── String allocator ─────────────────────────────────────────────────────
95
96    unsafe extern "C" fn string_alloc(
97        cookie: *mut c_void, length: i32, buffer: *mut *mut c_char,
98    ) -> bool {
99        if length < 0 { return true; }
100        let s = unsafe { &mut *(cookie as *mut StringBuf) };
101        s.0.reserve_exact(length as usize + 1);
102        unsafe { s.0.as_mut_vec().resize(length as usize + 1, 0) };
103        unsafe { *buffer = s.0.as_mut_ptr() as *mut c_char };
104        true
105    }
106
107    struct StringBuf(String);
108    impl StringBuf {
109        fn new() -> Self { Self(String::new()) }
110        fn finish(mut self) -> Option<String> {
111            if let Some(pos) = self.0.as_bytes().iter().position(|&b| b == 0) {
112                unsafe { self.0.as_mut_vec().truncate(pos) };
113            }
114            if self.0.is_empty() { None } else { Some(self.0) }
115        }
116    }
117
118    // ── Vtable ────────────────────────────────────────────────────────────────
119
120    struct Vtable {
121        get_service:         unsafe extern "C" fn(*const c_char) -> *mut AIBinder,
122        class_define:        unsafe extern "C" fn(
123                                 *const c_char,
124                                 unsafe extern "C" fn(*mut c_void) -> *mut c_void,
125                                 unsafe extern "C" fn(*mut c_void),
126                                 unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel) -> BinderStatus,
127                             ) -> *mut AIBinder_Class,
128        associate_class:     unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool,
129        new_binder:          unsafe extern "C" fn(*const AIBinder_Class, *mut c_void) -> *mut AIBinder,
130        prepare_transaction: unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus,
131        transact:            unsafe extern "C" fn(*mut AIBinder, u32, *mut *mut AParcel, *mut *mut AParcel, u32) -> BinderStatus,
132        dec_strong:          unsafe extern "C" fn(*mut AIBinder),
133        parcel_delete:       unsafe extern "C" fn(*mut AParcel),
134        read_int32:          unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus,
135        read_string:         unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus,
136        write_strong_binder: unsafe extern "C" fn(*mut AParcel, *mut AIBinder) -> BinderStatus,
137        set_thread_pool_max: unsafe extern "C" fn(u32),
138        join_thread_pool:    unsafe extern "C" fn(),
139        write_int32:         unsafe extern "C" fn(*mut AParcel, i32) -> BinderStatus,
140        // Optional: only present on API 29+, but all modern Android has this
141        read_bool:           Option<unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus>,
142    }
143
144    // ── RAII wrappers ─────────────────────────────────────────────────────────
145
146    struct DlHandle(*mut c_void);
147    unsafe impl Send for DlHandle {}
148    impl Drop for DlHandle {
149        fn drop(&mut self) {
150            // Intentionally no dlclose: the binder thread pool spawned in
151            // open_with_observer() keeps executing library code until process
152            // exit. Unloading the library while that thread runs causes
153            // use-after-free. libbinder_ndk.so is never unloaded during the
154            // daemon lifetime; the OS reclaims it on exit.
155        }
156    }
157
158    struct OwnedParcel { ptr: *mut AParcel, delete: unsafe extern "C" fn(*mut AParcel) }
159    impl Drop for OwnedParcel {
160        fn drop(&mut self) { if !self.ptr.is_null() { unsafe { (self.delete)(self.ptr) }; } }
161    }
162
163    struct OwnedBinder { ptr: *mut AIBinder, dec_strong: unsafe extern "C" fn(*mut AIBinder) }
164    unsafe impl Send for OwnedBinder {}
165    impl Drop for OwnedBinder {
166        fn drop(&mut self) { if !self.ptr.is_null() { unsafe { (self.dec_strong)(self.ptr) }; } }
167    }
168
169    // ── dlsym helper ─────────────────────────────────────────────────────────
170
171    macro_rules! dlsym_fn {
172        ($handle:expr, $name:literal, $ty:ty) => {{
173            let sym = unsafe {
174                libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char)
175            };
176            if sym.is_null() {
177                return Err(CoreError::binder(-1, concat!("dlsym:", $name)));
178            }
179            unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }
180        }};
181    }
182
183    macro_rules! dlsym_opt {
184        ($handle:expr, $name:literal, $ty:ty) => {{
185            let sym = unsafe {
186                libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char)
187            };
188            if sym.is_null() { None }
189            else { Some(unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }) }
190        }};
191    }
192
193    fn load_vtable(handle: *mut c_void) -> Result<Vtable, CoreError> {
194        Ok(Vtable {
195            get_service: dlsym_fn!(handle, "AServiceManager_getService",
196                unsafe extern "C" fn(*const c_char) -> *mut AIBinder),
197            class_define: dlsym_fn!(handle, "AIBinder_Class_define",
198                unsafe extern "C" fn(
199                    *const c_char,
200                    unsafe extern "C" fn(*mut c_void) -> *mut c_void,
201                    unsafe extern "C" fn(*mut c_void),
202                    unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel) -> BinderStatus,
203                ) -> *mut AIBinder_Class),
204            associate_class: dlsym_fn!(handle, "AIBinder_associateClass",
205                unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool),
206            new_binder: dlsym_fn!(handle, "AIBinder_new",
207                unsafe extern "C" fn(*const AIBinder_Class, *mut c_void) -> *mut AIBinder),
208            prepare_transaction: dlsym_fn!(handle, "AIBinder_prepareTransaction",
209                unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus),
210            transact: dlsym_fn!(handle, "AIBinder_transact",
211                unsafe extern "C" fn(*mut AIBinder, u32, *mut *mut AParcel, *mut *mut AParcel, u32) -> BinderStatus),
212            dec_strong: dlsym_fn!(handle, "AIBinder_decStrong",
213                unsafe extern "C" fn(*mut AIBinder)),
214            parcel_delete: dlsym_fn!(handle, "AParcel_delete",
215                unsafe extern "C" fn(*mut AParcel)),
216            read_int32: dlsym_fn!(handle, "AParcel_readInt32",
217                unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus),
218            read_string: dlsym_fn!(handle, "AParcel_readString",
219                unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus),
220            write_strong_binder: dlsym_fn!(handle, "AParcel_writeStrongBinder",
221                unsafe extern "C" fn(*mut AParcel, *mut AIBinder) -> BinderStatus),
222            set_thread_pool_max: dlsym_fn!(handle, "ABinderProcess_setThreadPoolMaxThreadCount",
223                unsafe extern "C" fn(u32)),
224            join_thread_pool: dlsym_fn!(handle, "ABinderProcess_joinThreadPool",
225                unsafe extern "C" fn()),
226            write_int32: dlsym_fn!(handle, "AParcel_writeInt32",
227                unsafe extern "C" fn(*mut AParcel, i32) -> BinderStatus),
228            read_bool: dlsym_opt!(handle, "AParcel_readBool",
229                unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus),
230        })
231    }
232
233    // ── ParcelReader ──────────────────────────────────────────────────────────
234
235    struct ParcelReader<'a> { vt: &'a Vtable, parcel: &'a OwnedParcel }
236
237    impl<'a> ParcelReader<'a> {
238        fn read_i32(&self) -> Result<i32, CoreError> {
239            let mut v = 0i32;
240            let s = unsafe { (self.vt.read_int32)(self.parcel.ptr, &mut v) };
241            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readInt32")); }
242            Ok(v)
243        }
244        fn read_string(&self) -> Result<Option<String>, CoreError> {
245            let mut buf = StringBuf::new();
246            let s = unsafe {
247                (self.vt.read_string)(self.parcel.ptr, &mut buf as *mut StringBuf as *mut c_void, string_alloc)
248            };
249            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readString")); }
250            Ok(buf.finish())
251        }
252        fn skip_i32s(&self, n: usize) -> Result<(), CoreError> {
253            for _ in 0..n { self.read_i32()?; }
254            Ok(())
255        }
256        fn skip_int_array(&self) -> Result<(), CoreError> {
257            let count = self.read_i32()?.max(0) as usize;
258            self.skip_i32s(count)
259        }
260        fn read_first_package_from_names(&self) -> Result<Option<String>, CoreError> {
261            let count = self.read_i32()?.max(0) as usize;
262            let mut first: Option<String> = None;
263            for _ in 0..count {
264                let s = self.read_string()?;
265                if first.is_none() {
266                    first = s.and_then(|c| c.split('/').next().map(str::to_owned));
267                }
268            }
269            Ok(first)
270        }
271    }
272
273    // ── Response parsers ──────────────────────────────────────────────────────
274
275    fn parse_root_task_info_body(r: &ParcelReader<'_>) -> Result<Option<String>, CoreError> {
276        let scratch = r.read_i32()?;
277        if scratch != 0 { r.skip_i32s(4)?; }
278        r.skip_int_array()?;
279        r.read_first_package_from_names()
280    }
281
282    fn parse_stack_info_body(r: &ParcelReader<'_>) -> Result<Option<String>, CoreError> {
283        r.skip_i32s(5)?;
284        r.skip_int_array()?;
285        r.read_first_package_from_names()
286    }
287
288    // ── Tx code resolution ────────────────────────────────────────────────────
289
290    pub struct TxCodes {
291        pub observer_code: u32,
292        pub query_code:    u32,
293        pub api_mode:      u8,  // 1 = RootTaskInfo, 2 = StackInfo
294        pub fg_code:       u32,
295    }
296
297    pub fn resolve_tx_codes() -> Result<TxCodes, CoreError> {
298        let (obs, query, api, fg) = dex::resolve_tx_codes_from_dex()
299            .ok_or_else(|| CoreError::binder(-1, "tx_code_resolution:dex_parse_failed"))?;
300        Ok(TxCodes { observer_code: obs, query_code: query, api_mode: api, fg_code: fg })
301    }
302
303    // ── ActivityManagerBinder ─────────────────────────────────────────────────
304
305    pub struct ActivityManagerBinder {
306        _lib:    DlHandle,
307        vt:      Vtable,
308        _class:  *mut AIBinder_Class,
309        service: OwnedBinder,
310        tx_code: u32,
311        legacy:  bool,
312    }
313    unsafe impl Send for ActivityManagerBinder {}
314
315    impl ActivityManagerBinder {
316        fn open_inner(handle: *mut c_void) -> Result<(DlHandle, Vtable, *mut AIBinder_Class, OwnedBinder), CoreError> {
317            let lib = DlHandle(handle);
318            let vt = load_vtable(handle)?;
319
320            let am_class = unsafe {
321                (vt.class_define)(
322                    AM_DESCRIPTOR.as_ptr() as *const c_char,
323                    am_on_create, am_on_destroy, am_on_transact,
324                )
325            };
326            if am_class.is_null() { return Err(CoreError::binder(-1, "AIBinder_Class_define:AM")); }
327
328            let raw = unsafe { (vt.get_service)(ACTIVITY_SERVICE.as_ptr() as *const c_char) };
329            if raw.is_null() { return Err(CoreError::binder(-1, "AServiceManager_getService:activity")); }
330            unsafe { (vt.associate_class)(raw, am_class) };
331
332            let service = OwnedBinder { ptr: raw, dec_strong: vt.dec_strong };
333            Ok((lib, vt, am_class, service))
334        }
335
336        fn dlopen_libbinder() -> Result<*mut c_void, CoreError> {
337            use std::os::raw::c_char;
338            let handle = unsafe {
339                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
340            };
341            if handle.is_null() { return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so")); }
342            Ok(handle)
343        }
344
345        /// Open ActivityManager binder (polling mode — no observer).
346        /// Resolves the query tx code from cache or DEX.
347        pub fn open() -> Result<Self, CoreError> {
348            let handle = Self::dlopen_libbinder()?;
349            let (lib, vt, class, service) = Self::open_inner(handle)?;
350            let codes = resolve_tx_codes()?;
351            let legacy = codes.api_mode == 2;
352            Ok(Self { _lib: lib, vt, _class: class, service, tx_code: codes.query_code, legacy })
353        }
354
355        /// Open ActivityManager binder and register as IProcessObserver.
356        ///
357        /// Returns `(Self, eventfd_raw_fd)`. The eventfd becomes readable
358        /// whenever `onForegroundActivitiesChanged` fires. Caller must add it
359        /// to epoll. After the event fires, call `get_focused_package`.
360        pub fn open_with_observer() -> Result<(Self, i32), CoreError> {
361            let handle = Self::dlopen_libbinder()?;
362            let (lib, vt, am_class, service) = Self::open_inner(handle)?;
363            let codes = resolve_tx_codes()?;
364            let legacy = codes.api_mode == 2;
365
366            // Create eventfd for callback → epoll bridge
367            let efd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) };
368            if efd < 0 { return Err(CoreError::sys(unsafe { *libc::__errno() }, "eventfd")); }
369
370            // Store fg_code and eventfd in statics for the callback
371            OBS_FG_CODE.store(codes.fg_code, Ordering::Relaxed);
372            OBS_EVENTFD.store(efd, Ordering::Relaxed);
373
374            // Define IProcessObserver class (we're the server)
375            let obs_class = unsafe {
376                (vt.class_define)(
377                    OBS_DESCRIPTOR.as_ptr() as *const c_char,
378                    obs_on_create, obs_on_destroy, obs_on_transact,
379                )
380            };
381            if obs_class.is_null() {
382                unsafe { libc::close(efd) };
383                return Err(CoreError::binder(-1, "AIBinder_Class_define:Observer"));
384            }
385
386            // Instantiate our observer binder object
387            let obs_binder = unsafe { (vt.new_binder)(obs_class, std::ptr::null_mut()) };
388            if obs_binder.is_null() {
389                unsafe { libc::close(efd) };
390                return Err(CoreError::binder(-1, "AIBinder_new:Observer"));
391            }
392            unsafe { (vt.associate_class)(obs_binder, obs_class) };
393
394            // Call registerProcessObserver(observer)
395            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
396            let s = unsafe { (vt.prepare_transaction)(service.ptr, &mut in_ptr) };
397            if s != STATUS_OK {
398                unsafe { libc::close(efd) };
399                return Err(CoreError::binder(s, "prepareTransaction:registerObserver"));
400            }
401            unsafe { (vt.write_strong_binder)(in_ptr, obs_binder) };
402            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
403            let s = unsafe {
404                (vt.transact)(service.ptr, codes.observer_code, &mut in_ptr, &mut out_ptr, 0)
405            };
406            if !out_ptr.is_null() { unsafe { (vt.parcel_delete)(out_ptr) }; }
407            if s != STATUS_OK {
408                unsafe { libc::close(efd) };
409                return Err(CoreError::binder(s, "transact:registerProcessObserver"));
410            }
411
412            // Start binder thread pool — blocks forever in background thread
413            unsafe { (vt.set_thread_pool_max)(0) };
414            let join_fn = vt.join_thread_pool;
415            std::thread::spawn(move || unsafe { join_fn() });
416
417            let binder = Self { _lib: lib, vt, _class: am_class, service, tx_code: codes.query_code, legacy };
418            Ok((binder, efd))
419        }
420
421        fn do_transact(&self) -> Result<OwnedParcel, CoreError> {
422            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
423            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut in_ptr) };
424            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
425            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
426            let s = unsafe {
427                (self.vt.transact)(self.service.ptr, self.tx_code, &mut in_ptr, &mut out_ptr, 0)
428            };
429            let out = OwnedParcel { ptr: out_ptr, delete: self.vt.parcel_delete };
430            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
431            Ok(out)
432        }
433
434        pub fn get_focused_package(&self) -> Result<Option<String>, CoreError> {
435            let out = self.do_transact()?;
436            let r = ParcelReader { vt: &self.vt, parcel: &out };
437            let ex = r.read_i32()?;
438            if ex != EX_NONE { return Err(CoreError::binder(ex, "getFocusedTask:exception")); }
439            let present = r.read_i32()?;
440            if present == 0 { return Ok(None); }
441            if self.legacy { parse_stack_info_body(&r) } else { parse_root_task_info_body(&r) }
442        }
443    }
444
445    // ── RawBinderService ──────────────────────────────────────────────────────
446
447    /// Generic binder client for any named Android service.
448    ///
449    /// Handles its own `dlopen` on `libbinder_ndk.so`. Callers provide raw
450    /// transaction codes (resolved via [`crate::dex::find_transaction_code`])
451    /// and use [`RawBinderService::transact_bool`] /
452    /// [`RawBinderService::transact_i32`] for typed round-trips.
453    pub struct RawBinderService {
454        _lib:    DlHandle,
455        vt:      Vtable,
456        service: OwnedBinder,
457    }
458    unsafe impl Send for RawBinderService {}
459
460    impl RawBinderService {
461        /// Open a connection to the named service (e.g. `"power"`, `"batterystats"`).
462        pub fn open(service_name: &str) -> Result<Self, CoreError> {
463            use std::ffi::CString;
464            let handle = unsafe {
465                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
466            };
467            if handle.is_null() {
468                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
469            }
470            let lib = DlHandle(handle);
471            let vt = load_vtable(handle)?;
472            let cs = CString::new(service_name)
473                .map_err(|_| CoreError::binder(-1, "service_name:nul_byte"))?;
474            let raw = unsafe { (vt.get_service)(cs.as_ptr()) };
475            if raw.is_null() {
476                return Err(CoreError::binder(-1, "AServiceManager_getService:null"));
477            }
478            let service = OwnedBinder { ptr: raw, dec_strong: vt.dec_strong };
479            Ok(Self { _lib: lib, vt, service })
480        }
481
482        /// Send a no-argument transaction; read exception header then bool reply.
483        pub fn transact_bool(&self, code: u32) -> Result<bool, CoreError> {
484            let out = self.raw_noarg(code)?;
485            let r = ParcelReader { vt: &self.vt, parcel: &out };
486            let ex = r.read_i32()?;
487            if ex != EX_NONE { return Err(CoreError::binder(ex, "transact_bool:exception")); }
488            if let Some(rb) = self.vt.read_bool {
489                let mut v = false;
490                let s = unsafe { rb(out.ptr as *const AParcel, &mut v) };
491                if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readBool")); }
492                Ok(v)
493            } else {
494                Ok(r.read_i32()? != 0)
495            }
496        }
497
498        /// Send a transaction with one i32 argument; discard reply.
499        pub fn transact_i32(&self, code: u32, arg: i32) -> Result<(), CoreError> {
500            let mut inp: *mut AParcel = std::ptr::null_mut();
501            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut inp) };
502            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
503            let s = unsafe { (self.vt.write_int32)(inp, arg) };
504            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_writeInt32")); }
505            let mut out: *mut AParcel = std::ptr::null_mut();
506            let s = unsafe { (self.vt.transact)(self.service.ptr, code, &mut inp, &mut out, 0) };
507            if !out.is_null() { unsafe { (self.vt.parcel_delete)(out) }; }
508            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
509            Ok(())
510        }
511
512        fn raw_noarg(&self, code: u32) -> Result<OwnedParcel, CoreError> {
513            let mut inp: *mut AParcel = std::ptr::null_mut();
514            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut inp) };
515            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
516            let mut out: *mut AParcel = std::ptr::null_mut();
517            let s = unsafe { (self.vt.transact)(self.service.ptr, code, &mut inp, &mut out, 0) };
518            let out = OwnedParcel { ptr: out, delete: self.vt.parcel_delete };
519            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
520            Ok(out)
521        }
522    }
523}
524
525// ── Public re-exports ─────────────────────────────────────────────────────────
526
527#[cfg(target_os = "android")]
528pub use imp::{ActivityManagerBinder, RawBinderService, TxCodes, resolve_tx_codes};
529
530// ── Non-Android stubs ─────────────────────────────────────────────────────────
531
532#[cfg(not(target_os = "android"))]
533pub struct ActivityManagerBinder;
534
535#[cfg(not(target_os = "android"))]
536impl ActivityManagerBinder {
537    pub fn open() -> Result<Self, crate::CoreError> {
538        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
539    }
540    pub fn open_with_observer() -> Result<(Self, i32), crate::CoreError> {
541        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
542    }
543    pub fn get_focused_package(&self) -> Result<Option<String>, crate::CoreError> {
544        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
545    }
546}
547
548#[cfg(not(target_os = "android"))]
549pub struct RawBinderService;
550
551#[cfg(not(target_os = "android"))]
552impl RawBinderService {
553    pub fn open(_service_name: &str) -> Result<Self, crate::CoreError> {
554        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
555    }
556    pub fn transact_bool(&self, _code: u32) -> Result<bool, crate::CoreError> {
557        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
558    }
559    pub fn transact_i32(&self, _code: u32, _arg: i32) -> Result<(), crate::CoreError> {
560        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
561    }
562}
563
564#[cfg(not(target_os = "android"))]
565pub struct TxCodes { pub observer_code: u32, pub query_code: u32, pub api_mode: u8, pub fg_code: u32 }
566
567#[cfg(not(target_os = "android"))]
568pub fn resolve_tx_codes() -> Result<TxCodes, crate::CoreError> {
569    Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
570}