cloudfox-coreshift-core 2.33.0

Low-level Linux and Android systems primitives for CoreShift (CloudFox)
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/

//! IAudioService playback-callback client: the **live audio event source**.
//!
//! Registers an `IPlaybackConfigDispatcher` binder with the audio service so
//! every playback-configuration change fires `dispatchPlaybackConfigChange`
//! on our binder (oneway), instead of polling
//! `getActivePlaybackConfigurations`. The utensil watcher's audio gate becomes
//! real-time like the screen and foreground observers: the daemon is woken the
//! moment music/cast/VoIP starts or stops.
//!
//! The delivered list is the full set of **currently-active** configs (AOSP
//! `PlaybackActivityMonitor.anonymizeForPublicConsumption` filters
//! `isActive()` players for non-privileged listeners), so the callback only
//! decodes the list count — `count > 0` ⟺ audio is playing (see
//! [`super::probe::decode_playback_dispatch`]). No config body is ever walked.
//!
//! ## Lifecycle
//!
//! [`PlaybackCallback::open`] resolves the tx codes, opens the service, builds
//! the dispatcher binder, creates and publishes the core-owned eventfd, and
//! starts the process-global binder thread pool. [`PlaybackCallback::register`]
//! submits the dispatcher to the service; [`PlaybackCallback::unregister`]
//! (a oneway call, per the AIDL) tears it down. The consumer receives a **dup**
//! of the eventfd and may close it freely (C2); the core keeps its own ref for
//! the callback's lifetime.

use super::probe::{ProbeRead, decode_playback_dispatch};
use super::sys::*;
use crate::CoreError;
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd};
use std::os::raw::{c_char, c_void};
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};

const AUDIO_SERVICE: &[u8] = b"audio\0";
const AUDIO_DESCRIPTOR: &[u8] = b"android.media.IAudioService\0";
const DISPATCHER_DESCRIPTOR: &[u8] = b"android.media.IPlaybackConfigDispatcher\0";

// Core owns the callback eventfd; the consumer gets a dup and may close it
// freely. Same lifetime discipline as the display callback and the
// ActivityManager observers (C2). Published BEFORE registration so a dispatch
// arriving between registration and publish is never lost (finding 26).
static AUDIO_EVENTFD: Mutex<Option<OwnedFd>> = Mutex::new(None);
/// The dispatcher tx code, published before registration so the callback never
/// races an unset value.
static AUDIO_DISPATCH_CODE: AtomicU32 = AtomicU32::new(1);
/// `AParcel_readInt32` fn pointer so the callback can decode the list count
/// without owning a `Vtable` (mirrors `FGPROC_READ_I32`); published non-zero
/// before the eventfd so a matching code is never paired with an unset reader.
static AUDIO_READ_I32: AtomicUsize = AtomicUsize::new(0);
/// Latest decoded signal: true when the delivered list holds ≥ 1 active
/// config. Read by [`PlaybackCallback::audio_active`] on an eventfd wake.
static AUDIO_ACTIVE: AtomicBool = AtomicBool::new(false);

fn audio_eventfd_guard() -> std::sync::MutexGuard<'static, Option<OwnedFd>> {
    AUDIO_EVENTFD.lock().unwrap_or_else(|p| p.into_inner())
}

/// `ProbeRead` over a framework-owned input `AParcel`, decoding with the raw
/// `AParcel_readInt32` fn pointer — the callback has no `Vtable` to build a
/// `ParcelReader` from. Only `read_i32` is used by the dispatch decode.
struct AudioInParcel {
    parcel: *const AParcel,
    read: unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus,
}

impl ProbeRead for AudioInParcel {
    fn read_i32(&mut self) -> Result<i32, ()> {
        let mut v = 0i32;
        if unsafe { (self.read)(self.parcel, &mut v) } == STATUS_OK {
            Ok(v)
        } else {
            Err(())
        }
    }
    fn skip_string(&mut self) -> Result<(), ()> {
        Ok(())
    }
    fn skip_binder(&mut self) -> Result<(), ()> {
        Ok(())
    }
}

// ── IPlaybackConfigDispatcher server callbacks ─────────────────────────────

unsafe extern "C" fn audio_cb_on_create(_: *mut c_void) -> *mut c_void {
    std::ptr::null_mut()
}
unsafe extern "C" fn audio_cb_on_destroy(_: *mut c_void) {}

/// The audio service's oneway `dispatchPlaybackConfigChange(configs, flush)`
/// on our dispatcher binder. Decodes the list count (the interface token is
/// already consumed by the framework before `on_transact`), stores the
/// audio-active signal, then wakes the consumer via the eventfd.
unsafe extern "C" fn audio_cb_on_transact(
    _: *mut AIBinder,
    code: u32,
    in_parcel: *const AParcel,
    _: *mut AParcel,
) -> BinderStatus {
    if code != AUDIO_DISPATCH_CODE.load(Ordering::Relaxed) {
        return STATUS_UNKNOWN_TRANSACTION;
    }
    // Reader is published non-zero before the dispatch code / eventfd, so a
    // matching code is never paired with an unset reader.
    let read_addr = AUDIO_READ_I32.load(Ordering::Relaxed);
    if read_addr != 0 {
        let read_fn: unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus =
            unsafe { std::mem::transmute(read_addr) };
        let mut r = AudioInParcel {
            parcel: in_parcel,
            read: read_fn,
        };
        match decode_playback_dispatch(&mut r) {
            Ok(active) => AUDIO_ACTIVE.store(active, Ordering::Relaxed),
            // A malformed parcel keeps the previous state; the consumer is
            // still woken so it can fall back to the one-shot probe.
            Err(_) => {}
        }
    }
    // Write while holding the lock: the fd can only be closed while we hold
    // it, so a revoke can never race us into a stale number.
    if let Some(fd) = audio_eventfd_guard().as_ref() {
        let val: u64 = 1;
        unsafe { libc::write(fd.as_raw_fd(), &val as *const u64 as *const c_void, 8) };
    }
    STATUS_OK
}

// ── IAudioService client callbacks ─────────────────────────────────────────

// Client-only: the NDK requires a class on ANY binder that participates in a
// transaction (AIBinder_prepareTransaction fails with
// STATUS_INVALID_OPERATION when getClass() == null) and writes the interface
// token from the class descriptor. These stubs are never invoked for a client
// (remote) binder — same pattern as display.rs / am.rs.
unsafe extern "C" fn audio_client_on_create(_: *mut c_void) -> *mut c_void {
    std::ptr::null_mut()
}
unsafe extern "C" fn audio_client_on_destroy(_: *mut c_void) {}
unsafe extern "C" fn audio_client_on_transact(
    _: *mut AIBinder,
    _: u32,
    _: *const AParcel,
    _: *mut AParcel,
) -> BinderStatus {
    STATUS_UNKNOWN_TRANSACTION
}

// ── PlaybackCallback ──────────────────────────────────────────────────────

/// The live audio event source: registration handle + eventfd wake.
///
/// The returned `crate::fd::Fd` is a dup of the core-owned eventfd, which
/// fires on every `dispatchPlaybackConfigChange` — i.e. on every actual
/// playback **change**. There is no initial dispatch at registration (AOSP
/// `PlayMonitorClient.init` only links to death), so a freshly registered
/// handle reports the untouched default state until the first change; the
/// authoritative current state comes from the one-shot
/// [`super::probe::audio_has_active_playback`] probe. On a wake,
/// [`PlaybackCallback::audio_active`] reports the decoded state at that
/// change (true = a player just became active, false = the last active player
/// just stopped).
pub struct PlaybackCallback {
    _lib: DlHandle,
    vt: Vtable,
    audio: OwnedBinder,
    dispatcher: OwnedBinder,
    register_tx: u32,
    unregister_tx: u32,
}
unsafe impl Send for PlaybackCallback {}

impl PlaybackCallback {
    /// Open the `audio` service, build the dispatcher binder, resolve the tx
    /// codes (register/unregister/dispatch) from the installed framework, and
    /// create + publish the callback eventfd. Returns the handle and a
    /// consumer dup of the eventfd.
    ///
    /// Registration is **not** performed here — call [`PlaybackCallback::register`]
    /// when the watch starts and [`PlaybackCallback::unregister`] when it ends,
    /// so the callback is only live while the utensil watcher needs it.
    pub fn open() -> Result<(Self, crate::fd::Fd), CoreError> {
        let handle = unsafe {
            libc::dlopen(
                LIBBINDER_PATH.as_ptr() as *const c_char,
                libc::RTLD_NOW | libc::RTLD_LOCAL,
            )
        };
        if handle.is_null() {
            return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
        }
        let lib = DlHandle;
        let vt = load_vtable(handle)?;

        let register_tx = crate::android::dex::resolve_register_playback_callback_tx()
            .ok_or_else(|| CoreError::binder(-1, "dex:TRANSACTION_registerPlaybackCallback"))?;
        let unregister_tx = crate::android::dex::resolve_unregister_playback_callback_tx()
            .ok_or_else(|| CoreError::binder(-1, "dex:TRANSACTION_unregisterPlaybackCallback"))?;
        let dispatch_code = crate::android::dex::resolve_dispatch_playback_config_change_tx()
            .ok_or_else(|| CoreError::binder(-1, "dex:TRANSACTION_dispatchPlaybackConfigChange"))?;

        // Client class + binder for the audio service. A client-only binder
        // still needs a class (AIBinder_prepareTransaction requires
        // getClass() != null and writes the interface token from it).
        let audio_class = unsafe {
            (vt.class_define)(
                AUDIO_DESCRIPTOR.as_ptr() as *const c_char,
                audio_client_on_create,
                audio_client_on_destroy,
                audio_client_on_transact,
            )
        };
        if audio_class.is_null() {
            return Err(CoreError::binder(-1, "AIBinder_Class_define:IAudioService"));
        }
        let raw_audio = unsafe { (vt.get_service)(AUDIO_SERVICE.as_ptr() as *const c_char) };
        if raw_audio.is_null() {
            return Err(CoreError::binder(-1, "AServiceManager_getService:audio"));
        }
        unsafe { (vt.associate_class)(raw_audio, audio_class) };
        let audio = OwnedBinder {
            ptr: raw_audio,
            dec_strong: vt.dec_strong,
        };

        // Dispatcher class + local binder (we're the server receiving
        // callbacks).
        let dispatcher_class = unsafe {
            (vt.class_define)(
                DISPATCHER_DESCRIPTOR.as_ptr() as *const c_char,
                audio_cb_on_create,
                audio_cb_on_destroy,
                audio_cb_on_transact,
            )
        };
        if dispatcher_class.is_null() {
            return Err(CoreError::binder(
                -1,
                "AIBinder_Class_define:PlaybackConfigDispatcher",
            ));
        }
        let raw_dispatcher = unsafe { (vt.new_binder)(dispatcher_class, std::ptr::null_mut()) };
        if raw_dispatcher.is_null() {
            return Err(CoreError::binder(
                -1,
                "AIBinder_new:PlaybackConfigDispatcher",
            ));
        }
        let dispatcher = OwnedBinder {
            ptr: raw_dispatcher,
            dec_strong: vt.dec_strong,
        };

        // Blocking eventfd (no EFD_NONBLOCK) — the callback writes, the
        // caller's read_u64_blocking() waits. The core owns it for the
        // callback's lifetime; the consumer receives a dup below (C2).
        let owned = unsafe {
            let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
            if raw < 0 {
                return Err(CoreError::sys(*libc::__errno(), "eventfd"));
            }
            OwnedFd::from_raw_fd(raw)
        };

        // Consumer dup — made before publishing, so an error path drops the
        // owned fd without ever leaving a stale handle for the callback.
        let efd_owned = owned
            .try_clone()
            .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:audio"))
            .and_then(|dup| unsafe {
                crate::fd::Fd::from_owned_raw_fd(dup.into_raw_fd(), "audio.efd")
                    .map_err(|_| CoreError::binder(-1, "Fd::from_owned_raw_fd:audio.efd"))
            })?;

        // Publish reader fn + dispatch code + eventfd BEFORE any transaction
        // (finding 26); reset the signal so a stale dispatch cannot mislead a
        // fresh registration.
        AUDIO_READ_I32.store(vt.read_int32 as usize, Ordering::Relaxed);
        AUDIO_DISPATCH_CODE.store(dispatch_code, Ordering::Relaxed);
        AUDIO_ACTIVE.store(false, Ordering::Relaxed);
        *audio_eventfd_guard() = Some(owned);

        // Join the single process-global binder thread pool so dispatcher
        // callbacks can fire (finding 22).
        start_binder_thread_pool(&vt);

        Ok((
            Self {
                _lib: lib,
                vt,
                audio,
                dispatcher,
                register_tx,
                unregister_tx,
            },
            efd_owned,
        ))
    }

    /// Register the dispatcher binder with the audio service. The callback
    /// fires on every subsequent playback **change** (there is no initial
    /// dispatch at registration — AOSP `PlayMonitorClient.init` only links to
    /// death). Pair with the one-shot
    /// [`super::probe::audio_has_active_playback`] probe for the authoritative
    /// current state.
    pub fn register(&self) -> Result<(), CoreError> {
        // Surface a rejected registration (finding 25).
        transact_write_checked(&self.vt, self.audio.ptr, self.register_tx, |w| {
            w.write_strong_binder(self.dispatcher.ptr)
        })
    }

    /// Unregister the dispatcher binder and stop publishing the eventfd: no
    /// further dispatches can wake a consumer after teardown. No-op at the
    /// framework level if not registered.
    ///
    /// `unregisterPlaybackCallback` is declared `oneway` in the AIDL, so the
    /// call is fire-and-forget with no reply parcel.
    pub fn unregister(&self) -> Result<(), CoreError> {
        let res = transact_oneway(&self.vt, self.audio.ptr, self.unregister_tx, |w| {
            w.write_strong_binder(self.dispatcher.ptr)
        });
        *audio_eventfd_guard() = None;
        AUDIO_ACTIVE.store(false, Ordering::Relaxed);
        res
    }

    /// Whether the last playback dispatch reported any active config — audio
    /// is playing right now. Read after an eventfd wake.
    pub fn audio_active(&self) -> bool {
        AUDIO_ACTIVE.load(Ordering::Relaxed)
    }
}

impl Drop for PlaybackCallback {
    /// Best-effort deregistration from the audio service. Same deliberate
    /// non-release of the local strong ref as [`super::task_stack::TaskStackListener`]
    /// — the userdata eventfd stays valid for any in-flight wake, and the
    /// framework-side registration is gone after the unregister.
    fn drop(&mut self) {
        let _ = self.unregister();
    }
}