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