use crate::CoreError;
pub(crate) const PLAYER_STATE_STARTED: i32 = 2;
const ATTR_PARCEL_IS_NULL_BUNDLE: i32 = -1977;
const CONFIG_LEAD: usize = 7;
const ATTR_FIXED: usize = 5;
#[cfg(target_os = "android")]
pub fn audio_has_active_playback() -> Result<bool, CoreError> {
use super::raw::RawBinderService;
use super::sys::ParcelReader;
use crate::android::dex::resolve_active_playback_configurations_tx;
let svc = RawBinderService::open("audio", "android.media.IAudioService")?;
let tx = resolve_active_playback_configurations_tx()
.ok_or_else(|| CoreError::binder(-1, "dex:TRANSACTION_getActivePlaybackConfigurations"))?;
let out = svc.raw_noarg(tx)?;
let mut r = ParcelReader::owned(svc.vtable(), &out);
decode_audio_active(&mut r)
}
pub(crate) trait ProbeRead {
fn read_i32(&mut self) -> Result<i32, ()>;
fn skip_string(&mut self) -> Result<(), ()>;
fn skip_binder(&mut self) -> Result<(), ()>;
}
#[cfg(target_os = "android")]
impl<'a> ProbeRead for super::sys::ParcelReader<'a> {
fn read_i32(&mut self) -> Result<i32, ()> {
super::sys::ParcelReader::read_i32(self).map_err(|_| ())
}
fn skip_string(&mut self) -> Result<(), ()> {
super::sys::ParcelReader::skip_string16(self)
.map(|_| ())
.map_err(|_| ())
}
fn skip_binder(&mut self) -> Result<(), ()> {
super::sys::ParcelReader::read_strong_binder(self)
.map(|_| ())
.map_err(|_| ())
}
}
pub(crate) fn decode_audio_active(r: &mut impl ProbeRead) -> Result<bool, CoreError> {
let ex = r
.read_i32()
.map_err(|_| CoreError::binder(-1, "audio:exception"))?;
if ex != crate::binder::wire::EX_NONE {
return Err(CoreError::binder(ex, "audio_active:exception"));
}
let count = r
.read_i32()
.map_err(|_| CoreError::binder(-1, "audio:count"))?;
if count < 0 {
return Err(CoreError::binder(-1, "audio_active:negative_count"));
}
for _ in 0..count {
let marker = r
.read_i32()
.map_err(|_| CoreError::binder(-1, "audio:marker"))?;
if marker != 1 {
return Err(CoreError::binder(-1, "audio_active:null_config"));
}
for _ in 0..CONFIG_LEAD - 1 {
r.read_i32()
.map_err(|_| CoreError::binder(-1, "audio:field"))?;
}
let state = r
.read_i32()
.map_err(|_| CoreError::binder(-1, "audio:state"))?;
if state == PLAYER_STATE_STARTED {
return Ok(true);
}
for _ in 0..ATTR_FIXED {
r.read_i32()
.map_err(|_| CoreError::binder(-1, "audio:attr"))?;
}
let tags = r
.read_i32()
.map_err(|_| CoreError::binder(-1, "audio:tags"))?;
if tags < 0 {
return Err(CoreError::binder(-1, "audio_active:bad_tags"));
}
for _ in 0..tags {
r.skip_string()
.map_err(|_| CoreError::binder(-1, "audio:tag"))?;
}
let bundle = r
.read_i32()
.map_err(|_| CoreError::binder(-1, "audio:bundle"))?;
if bundle != ATTR_PARCEL_IS_NULL_BUNDLE {
return Err(CoreError::binder(-1, "audio_active:unexpected_bundle"));
}
r.skip_binder()
.map_err(|_| CoreError::binder(-1, "audio:binder"))?;
r.read_i32()
.map_err(|_| CoreError::binder(-1, "audio:session"))?;
r.read_i32()
.map_err(|_| CoreError::binder(-1, "audio:spatialized"))?;
r.read_i32()
.map_err(|_| CoreError::binder(-1, "audio:channel"))?;
r.read_i32()
.map_err(|_| CoreError::binder(-1, "audio:rate"))?;
}
Ok(false)
}
#[cfg(test)]
mod tests {
use super::*;
const BINDER_WORDS: usize = 7;
struct I32Cursor {
words: Vec<i32>,
pos: usize,
}
impl I32Cursor {
fn from_words(words: Vec<i32>) -> Self {
Self { words, pos: 0 }
}
}
impl ProbeRead for I32Cursor {
fn read_i32(&mut self) -> Result<i32, ()> {
let v = self.words.get(self.pos).copied().ok_or(())?;
self.pos += 1;
Ok(v)
}
fn skip_string(&mut self) -> Result<(), ()> {
let len = self.read_i32()?;
if len < 0 {
return Ok(());
}
let words = (len as usize)
.saturating_add(1)
.saturating_mul(2)
.div_ceil(4);
self.pos += words;
if self.pos > self.words.len() {
return Err(());
}
Ok(())
}
fn skip_binder(&mut self) -> Result<(), ()> {
self.pos += BINDER_WORDS;
if self.pos > self.words.len() {
return Err(());
}
Ok(())
}
}
fn cfg(
piid: i32,
device_id: i32,
player_type: i32,
uid: i32,
pid: i32,
state: i32,
usage: i32,
content_type: i32,
) -> Vec<i32> {
let mut w = vec![1]; w.extend_from_slice(&[piid, device_id, 0, player_type, uid, pid, state]);
w.extend_from_slice(&[usage, content_type, -1, 0, 0, 0, ATTR_PARCEL_IS_NULL_BUNDLE]);
w.extend(vec![0; BINDER_WORDS]); w.push(0); w.extend_from_slice(&[0, 2, 48_000]);
w
}
fn reply(configs: Vec<Vec<i32>>) -> Vec<i32> {
let mut w = vec![0, configs.len() as i32];
for c in configs {
w.extend(c);
}
w
}
#[test]
fn active_config_detected() {
let words = reply(vec![cfg(
103,
0,
3,
1000,
1644,
PLAYER_STATE_STARTED,
13,
4,
)]);
let mut c = I32Cursor::from_words(words);
assert!(decode_audio_active(&mut c).unwrap());
assert_eq!(c.pos, 2 + 1 + 7);
}
#[test]
fn idle_configs_are_not_active() {
let words = reply(vec![
cfg(103, 0, 3, 1000, 1644, 1, 13, 4),
cfg(111, 0, 3, 10145, 2831, 1, 13, 4),
]);
let mut c = I32Cursor::from_words(words);
assert!(!decode_audio_active(&mut c).unwrap());
assert_eq!(c.pos, 2 + 2 * 26);
}
#[test]
fn zero_configs_is_not_active() {
let mut c = I32Cursor::from_words(vec![0, 0]);
assert!(!decode_audio_active(&mut c).unwrap());
}
#[test]
fn exception_reply_is_an_error() {
let mut c = I32Cursor::from_words(vec![1, 0]);
assert!(decode_audio_active(&mut c).is_err());
}
#[test]
fn truncated_reply_is_an_error() {
let mut c = I32Cursor::from_words(vec![0, 1]);
assert!(decode_audio_active(&mut c).is_err());
}
#[test]
fn negative_count_is_an_error() {
let mut c = I32Cursor::from_words(vec![0, -3]);
assert!(decode_audio_active(&mut c).is_err());
}
#[test]
fn null_config_marker_is_an_error() {
let mut c = I32Cursor::from_words(vec![0, 1, 0]);
assert!(decode_audio_active(&mut c).is_err());
}
#[test]
fn music_playing_config_is_active() {
let words = reply(vec![
cfg(103, 0, 3, 1000, 1644, 1, 13, 4),
cfg(111, 0, 3, 10145, 2831, 1, 13, 4),
cfg(17727, 3, 1, 10195, 27644, PLAYER_STATE_STARTED, 1, 2),
]);
let mut c = I32Cursor::from_words(words);
assert!(decode_audio_active(&mut c).unwrap());
assert_eq!(c.pos, 2 + 2 * 26 + 1 + 7);
}
#[test]
fn idle_config_with_tags_is_skipped() {
let mut words = vec![0, 2]; let mut c1 = vec![1];
c1.extend_from_slice(&[103, 0, 0, 3, 1000, 1644, 1]); c1.extend_from_slice(&[13, 4, -1, 0, 0]); c1.push(1); c1.extend_from_slice(&[4, 0, 0, 0]);
c1.push(ATTR_PARCEL_IS_NULL_BUNDLE);
c1.extend(vec![0; BINDER_WORDS]);
c1.push(0); c1.extend_from_slice(&[0, 2, 48_000]);
words.extend(c1);
words.extend(cfg(17727, 3, 1, 10195, 27644, PLAYER_STATE_STARTED, 1, 2));
let mut c = I32Cursor::from_words(words);
assert!(decode_audio_active(&mut c).unwrap());
assert_eq!(c.pos, 2 + 30 + 1 + 7);
}
}