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//! Resolution order (no subprocess required):
13//!
14//! 1. `tx_code.txt` cache (written by fgw or a previous run).
15//! 2. Parse `framework.jar` DEX — reads `TRANSACTION_*` static int fields
16//!    from `IActivityManager$Stub` and `IProcessObserver$Stub` directly.
17//!
18//! ## Observer mode
19//!
20//! `ActivityManagerBinder::open_with_observer` registers this process as an
21//! `IProcessObserver` with ActivityManager. Callbacks fire when foreground
22//! activities change and signal an `eventfd` that callers can poll via epoll.
23//! After the eventfd fires, call `get_focused_package` to read the new value.
24
25use crate::CoreError;
26
27// ─────────────────────────────────────────────────────────────────────────────
28// Android-only implementation
29// ─────────────────────────────────────────────────────────────────────────────
30
31#[cfg(target_os = "android")]
32mod imp {
33    use super::*;
34    use crate::dex;
35    use std::os::raw::{c_char, c_void};
36    use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
37
38    // ── NDK binder status codes ───────────────────────────────────────────────
39
40    const STATUS_OK: i32 = 0;
41    const STATUS_UNKNOWN_TRANSACTION: i32 = -2;
42    const EX_NONE: i32 = 0;
43
44    // ── Interface constants ───────────────────────────────────────────────────
45
46    const AM_DESCRIPTOR:    &[u8] = b"android.app.IActivityManager\0";
47    const OBS_DESCRIPTOR:   &[u8] = b"android.app.IProcessObserver\0";
48    const ACTIVITY_SERVICE: &[u8] = b"activity\0";
49    const LIBBINDER_PATH:   &[u8] = b"/system/lib64/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    // These are set once during observer setup before joinThreadPool, and then
57    // only read from the callback. Safe to access from multiple binder threads.
58
59    static OBS_FG_CODE: AtomicU32 = AtomicU32::new(0);
60    static OBS_EVENTFD:  AtomicI32 = AtomicI32::new(-1);
61
62    // ── Raw NDK type aliases ──────────────────────────────────────────────────
63
64    type AIBinder = c_void;
65    #[allow(non_camel_case_types)]
66    type AIBinder_Class = c_void;
67    type AParcel = c_void;
68    type BinderStatus = i32;
69    type StringAllocator = unsafe extern "C" fn(*mut c_void, i32, *mut *mut c_char) -> bool;
70
71    // ── AIBinder_Class callbacks ──────────────────────────────────────────────
72
73    // AM client — no-op server side (we're a client only)
74    unsafe extern "C" fn am_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
75    unsafe extern "C" fn am_on_destroy(_: *mut c_void) {}
76    unsafe extern "C" fn am_on_transact(
77        _: *mut AIBinder, _: u32, _: *const AParcel, _: *mut AParcel,
78    ) -> BinderStatus { STATUS_UNKNOWN_TRANSACTION }
79
80    // IProcessObserver server callbacks
81    unsafe extern "C" fn obs_on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
82    unsafe extern "C" fn obs_on_destroy(_: *mut c_void) {}
83    unsafe extern "C" fn obs_on_transact(
84        _: *mut AIBinder, code: u32, _: *const AParcel, _: *mut AParcel,
85    ) -> BinderStatus {
86        if code == OBS_FG_CODE.load(Ordering::Relaxed) {
87            let efd = OBS_EVENTFD.load(Ordering::Relaxed);
88            if efd >= 0 {
89                let val: u64 = 1;
90                unsafe { libc::write(efd, &val as *const u64 as *const c_void, 8) };
91            }
92        }
93        STATUS_OK
94    }
95
96    // ── String allocator ─────────────────────────────────────────────────────
97
98    unsafe extern "C" fn string_alloc(
99        cookie: *mut c_void, length: i32, buffer: *mut *mut c_char,
100    ) -> bool {
101        if length < 0 { return true; }
102        let s = unsafe { &mut *(cookie as *mut StringBuf) };
103        s.0.reserve_exact(length as usize + 1);
104        unsafe { s.0.as_mut_vec().resize(length as usize + 1, 0) };
105        unsafe { *buffer = s.0.as_mut_ptr() as *mut c_char };
106        true
107    }
108
109    struct StringBuf(String);
110    impl StringBuf {
111        fn new() -> Self { Self(String::new()) }
112        fn finish(mut self) -> Option<String> {
113            if let Some(pos) = self.0.as_bytes().iter().position(|&b| b == 0) {
114                unsafe { self.0.as_mut_vec().truncate(pos) };
115            }
116            if self.0.is_empty() { None } else { Some(self.0) }
117        }
118    }
119
120    // ── Vtable ────────────────────────────────────────────────────────────────
121
122    struct Vtable {
123        get_service:         unsafe extern "C" fn(*const c_char) -> *mut AIBinder,
124        class_define:        unsafe extern "C" fn(
125                                 *const c_char,
126                                 unsafe extern "C" fn(*mut c_void) -> *mut c_void,
127                                 unsafe extern "C" fn(*mut c_void),
128                                 unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel) -> BinderStatus,
129                             ) -> *mut AIBinder_Class,
130        associate_class:     unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool,
131        new_binder:          unsafe extern "C" fn(*const AIBinder_Class, *mut c_void) -> *mut AIBinder,
132        prepare_transaction: unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus,
133        transact:            unsafe extern "C" fn(*mut AIBinder, u32, *mut *mut AParcel, *mut *mut AParcel, u32) -> BinderStatus,
134        dec_strong:          unsafe extern "C" fn(*mut AIBinder),
135        parcel_delete:       unsafe extern "C" fn(*mut AParcel),
136        read_int32:          unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus,
137        read_string:         unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus,
138        write_strong_binder: unsafe extern "C" fn(*mut AParcel, *mut AIBinder) -> BinderStatus,
139        set_thread_pool_max: unsafe extern "C" fn(u32),
140        join_thread_pool:    unsafe extern "C" fn(),
141        // Optional: only present on API 29+, but all modern Android has this
142        #[allow(dead_code)]
143        read_bool:           Option<unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus>,
144    }
145
146    // ── RAII wrappers ─────────────────────────────────────────────────────────
147
148    struct DlHandle(*mut c_void);
149    unsafe impl Send for DlHandle {}
150    impl Drop for DlHandle {
151        fn drop(&mut self) {
152            // Intentionally no dlclose: the binder thread pool spawned in
153            // open_with_observer() keeps executing library code until process
154            // exit. Unloading the library while that thread runs causes
155            // use-after-free. libbinder_ndk.so is never unloaded during the
156            // daemon lifetime; the OS reclaims it on exit.
157        }
158    }
159
160    struct OwnedParcel { ptr: *mut AParcel, delete: unsafe extern "C" fn(*mut AParcel) }
161    impl Drop for OwnedParcel {
162        fn drop(&mut self) { if !self.ptr.is_null() { unsafe { (self.delete)(self.ptr) }; } }
163    }
164
165    struct OwnedBinder { ptr: *mut AIBinder, dec_strong: unsafe extern "C" fn(*mut AIBinder) }
166    unsafe impl Send for OwnedBinder {}
167    impl Drop for OwnedBinder {
168        fn drop(&mut self) { if !self.ptr.is_null() { unsafe { (self.dec_strong)(self.ptr) }; } }
169    }
170
171    // ── dlsym helper ─────────────────────────────────────────────────────────
172
173    macro_rules! dlsym_fn {
174        ($handle:expr, $name:literal, $ty:ty) => {{
175            let sym = unsafe {
176                libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char)
177            };
178            if sym.is_null() {
179                return Err(CoreError::binder(-1, concat!("dlsym:", $name)));
180            }
181            unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }
182        }};
183    }
184
185    macro_rules! dlsym_opt {
186        ($handle:expr, $name:literal, $ty:ty) => {{
187            let sym = unsafe {
188                libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char)
189            };
190            if sym.is_null() { None }
191            else { Some(unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }) }
192        }};
193    }
194
195    fn load_vtable(handle: *mut c_void) -> Result<Vtable, CoreError> {
196        Ok(Vtable {
197            get_service: dlsym_fn!(handle, "AServiceManager_getService",
198                unsafe extern "C" fn(*const c_char) -> *mut AIBinder),
199            class_define: dlsym_fn!(handle, "AIBinder_Class_define",
200                unsafe extern "C" fn(
201                    *const c_char,
202                    unsafe extern "C" fn(*mut c_void) -> *mut c_void,
203                    unsafe extern "C" fn(*mut c_void),
204                    unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel) -> BinderStatus,
205                ) -> *mut AIBinder_Class),
206            associate_class: dlsym_fn!(handle, "AIBinder_associateClass",
207                unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool),
208            new_binder: dlsym_fn!(handle, "AIBinder_new",
209                unsafe extern "C" fn(*const AIBinder_Class, *mut c_void) -> *mut AIBinder),
210            prepare_transaction: dlsym_fn!(handle, "AIBinder_prepareTransaction",
211                unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus),
212            transact: dlsym_fn!(handle, "AIBinder_transact",
213                unsafe extern "C" fn(*mut AIBinder, u32, *mut *mut AParcel, *mut *mut AParcel, u32) -> BinderStatus),
214            dec_strong: dlsym_fn!(handle, "AIBinder_decStrong",
215                unsafe extern "C" fn(*mut AIBinder)),
216            parcel_delete: dlsym_fn!(handle, "AParcel_delete",
217                unsafe extern "C" fn(*mut AParcel)),
218            read_int32: dlsym_fn!(handle, "AParcel_readInt32",
219                unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus),
220            read_string: dlsym_fn!(handle, "AParcel_readString",
221                unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus),
222            write_strong_binder: dlsym_fn!(handle, "AParcel_writeStrongBinder",
223                unsafe extern "C" fn(*mut AParcel, *mut AIBinder) -> BinderStatus),
224            set_thread_pool_max: dlsym_fn!(handle, "ABinderProcess_setThreadPoolMaxThreadCount",
225                unsafe extern "C" fn(u32)),
226            join_thread_pool: dlsym_fn!(handle, "ABinderProcess_joinThreadPool",
227                unsafe extern "C" fn()),
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 read_tx_cache(cache_path: &str) -> Option<TxCodes> {
298        let text = std::fs::read_to_string(cache_path).ok()?;
299        let mut parts = text.split_whitespace();
300        let obs:   u32 = parts.next()?.parse().ok()?;
301        let query: u32 = parts.next()?.parse().ok()?;
302        let api:   u8  = parts.next()?.parse().ok()?;
303        let fg:    u32 = parts.next()?.parse().ok()?;
304        // All four must be nonzero and api_mode must be valid (watcher.c rule)
305        if obs == 0 || query == 0 || fg == 0 || (api != 1 && api != 2) { return None; }
306        Some(TxCodes { observer_code: obs, query_code: query, api_mode: api, fg_code: fg })
307    }
308
309    pub fn write_tx_cache(cache_path: &str, codes: &TxCodes) {
310        let _ = std::fs::create_dir_all(
311            std::path::Path::new(cache_path).parent().unwrap_or(std::path::Path::new("/"))
312        );
313        let _ = std::fs::write(
314            cache_path,
315            format!("{} {} {} {}\n", codes.observer_code, codes.query_code, codes.api_mode, codes.fg_code),
316        );
317    }
318
319    pub fn resolve_tx_codes(cache_path: &str) -> Result<TxCodes, CoreError> {
320        if let Some(codes) = read_tx_cache(cache_path) {
321            return Ok(codes);
322        }
323        let (obs, query, api, fg) = dex::resolve_tx_codes_from_dex()
324            .ok_or_else(|| CoreError::binder(-1, "tx_code_resolution:dex_parse_failed"))?;
325        let codes = TxCodes { observer_code: obs, query_code: query, api_mode: api, fg_code: fg };
326        write_tx_cache(cache_path, &codes);
327        Ok(codes)
328    }
329
330    // ── ActivityManagerBinder ─────────────────────────────────────────────────
331
332    pub struct ActivityManagerBinder {
333        _lib:    DlHandle,
334        vt:      Vtable,
335        _class:  *mut AIBinder_Class,
336        service: OwnedBinder,
337        tx_code: u32,
338        legacy:  bool,
339    }
340    unsafe impl Send for ActivityManagerBinder {}
341
342    impl ActivityManagerBinder {
343        fn open_inner(handle: *mut c_void) -> Result<(DlHandle, Vtable, *mut AIBinder_Class, OwnedBinder), CoreError> {
344            let lib = DlHandle(handle);
345            let vt = load_vtable(handle)?;
346
347            let am_class = unsafe {
348                (vt.class_define)(
349                    AM_DESCRIPTOR.as_ptr() as *const c_char,
350                    am_on_create, am_on_destroy, am_on_transact,
351                )
352            };
353            if am_class.is_null() { return Err(CoreError::binder(-1, "AIBinder_Class_define:AM")); }
354
355            let raw = unsafe { (vt.get_service)(ACTIVITY_SERVICE.as_ptr() as *const c_char) };
356            if raw.is_null() { return Err(CoreError::binder(-1, "AServiceManager_getService:activity")); }
357            unsafe { (vt.associate_class)(raw, am_class) };
358
359            let service = OwnedBinder { ptr: raw, dec_strong: vt.dec_strong };
360            Ok((lib, vt, am_class, service))
361        }
362
363        fn dlopen_libbinder() -> Result<*mut c_void, CoreError> {
364            use std::os::raw::c_char;
365            let handle = unsafe {
366                libc::dlopen(LIBBINDER_PATH.as_ptr() as *const c_char, libc::RTLD_NOW | libc::RTLD_LOCAL)
367            };
368            if handle.is_null() { return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so")); }
369            Ok(handle)
370        }
371
372        /// Open ActivityManager binder (polling mode — no observer).
373        /// Resolves the query tx code from cache or DEX.
374        pub fn open(cache_path: &str) -> Result<Self, CoreError> {
375            let handle = Self::dlopen_libbinder()?;
376            let (lib, vt, class, service) = Self::open_inner(handle)?;
377            let codes = resolve_tx_codes(cache_path)?;
378            let legacy = codes.api_mode == 2;
379            Ok(Self { _lib: lib, vt, _class: class, service, tx_code: codes.query_code, legacy })
380        }
381
382        /// Open ActivityManager binder and register as IProcessObserver.
383        ///
384        /// Returns `(Self, eventfd_raw_fd)`. The eventfd becomes readable
385        /// whenever `onForegroundActivitiesChanged` fires. Caller must add it
386        /// to epoll. After the event fires, call `get_focused_package`.
387        pub fn open_with_observer(cache_path: &str) -> Result<(Self, i32), CoreError> {
388            let handle = Self::dlopen_libbinder()?;
389            let (lib, vt, am_class, service) = Self::open_inner(handle)?;
390            let codes = resolve_tx_codes(cache_path)?;
391            let legacy = codes.api_mode == 2;
392
393            // Create eventfd for callback → epoll bridge
394            let efd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) };
395            if efd < 0 { return Err(CoreError::sys(unsafe { *libc::__errno() }, "eventfd")); }
396
397            // Store fg_code and eventfd in statics for the callback
398            OBS_FG_CODE.store(codes.fg_code, Ordering::Relaxed);
399            OBS_EVENTFD.store(efd, Ordering::Relaxed);
400
401            // Define IProcessObserver class (we're the server)
402            let obs_class = unsafe {
403                (vt.class_define)(
404                    OBS_DESCRIPTOR.as_ptr() as *const c_char,
405                    obs_on_create, obs_on_destroy, obs_on_transact,
406                )
407            };
408            if obs_class.is_null() {
409                unsafe { libc::close(efd) };
410                return Err(CoreError::binder(-1, "AIBinder_Class_define:Observer"));
411            }
412
413            // Instantiate our observer binder object
414            let obs_binder = unsafe { (vt.new_binder)(obs_class, std::ptr::null_mut()) };
415            if obs_binder.is_null() {
416                unsafe { libc::close(efd) };
417                return Err(CoreError::binder(-1, "AIBinder_new:Observer"));
418            }
419            unsafe { (vt.associate_class)(obs_binder, obs_class) };
420
421            // Call registerProcessObserver(observer)
422            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
423            let s = unsafe { (vt.prepare_transaction)(service.ptr, &mut in_ptr) };
424            if s != STATUS_OK {
425                unsafe { libc::close(efd) };
426                return Err(CoreError::binder(s, "prepareTransaction:registerObserver"));
427            }
428            unsafe { (vt.write_strong_binder)(in_ptr, obs_binder) };
429            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
430            let s = unsafe {
431                (vt.transact)(service.ptr, codes.observer_code, &mut in_ptr, &mut out_ptr, 0)
432            };
433            if !out_ptr.is_null() { unsafe { (vt.parcel_delete)(out_ptr) }; }
434            if s != STATUS_OK {
435                unsafe { libc::close(efd) };
436                return Err(CoreError::binder(s, "transact:registerProcessObserver"));
437            }
438
439            // Start binder thread pool — blocks forever in background thread
440            unsafe { (vt.set_thread_pool_max)(0) };
441            let join_fn = vt.join_thread_pool;
442            std::thread::spawn(move || unsafe { join_fn() });
443
444            let binder = Self { _lib: lib, vt, _class: am_class, service, tx_code: codes.query_code, legacy };
445            Ok((binder, efd))
446        }
447
448        fn do_transact(&self) -> Result<OwnedParcel, CoreError> {
449            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
450            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut in_ptr) };
451            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_prepareTransaction")); }
452            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
453            let s = unsafe {
454                (self.vt.transact)(self.service.ptr, self.tx_code, &mut in_ptr, &mut out_ptr, 0)
455            };
456            let out = OwnedParcel { ptr: out_ptr, delete: self.vt.parcel_delete };
457            if s != STATUS_OK { return Err(CoreError::binder(s, "AIBinder_transact")); }
458            Ok(out)
459        }
460
461        pub fn get_focused_package(&self) -> Result<Option<String>, CoreError> {
462            let out = self.do_transact()?;
463            let r = ParcelReader { vt: &self.vt, parcel: &out };
464            let ex = r.read_i32()?;
465            if ex != EX_NONE { return Err(CoreError::binder(ex, "getFocusedTask:exception")); }
466            let present = r.read_i32()?;
467            if present == 0 { return Ok(None); }
468            if self.legacy { parse_stack_info_body(&r) } else { parse_root_task_info_body(&r) }
469        }
470    }
471}
472
473// ── Public re-exports ─────────────────────────────────────────────────────────
474
475#[cfg(target_os = "android")]
476pub use imp::{ActivityManagerBinder, TxCodes, read_tx_cache, write_tx_cache, resolve_tx_codes};
477
478// ── Non-Android stubs ─────────────────────────────────────────────────────────
479
480#[cfg(not(target_os = "android"))]
481pub struct ActivityManagerBinder;
482
483#[cfg(not(target_os = "android"))]
484impl ActivityManagerBinder {
485    pub fn open(_cache_path: &str) -> Result<Self, crate::CoreError> {
486        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
487    }
488    pub fn open_with_observer(_cache_path: &str) -> Result<(Self, i32), crate::CoreError> {
489        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
490    }
491    pub fn get_focused_package(&self) -> Result<Option<String>, crate::CoreError> {
492        Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
493    }
494}
495
496#[cfg(not(target_os = "android"))]
497pub struct TxCodes { pub observer_code: u32, pub query_code: u32, pub api_mode: u8, pub fg_code: u32 }
498
499#[cfg(not(target_os = "android"))]
500pub fn read_tx_cache(_: &str) -> Option<TxCodes> { None }
501#[cfg(not(target_os = "android"))]
502pub fn write_tx_cache(_: &str, _: &TxCodes) {}
503#[cfg(not(target_os = "android"))]
504pub fn resolve_tx_codes(_: &str) -> Result<TxCodes, crate::CoreError> {
505    Err(crate::CoreError::binder(-1, "binder:unsupported platform"))
506}