use std::collections::{HashSet, VecDeque};
use std::ffi::{CString, c_char};
use std::sync::{LazyLock, Mutex};
use super::{AdEvent, AdFormat};
#[derive(Default)]
struct Fake {
use_test_ads: bool,
consent: i32,
can_request_ads: bool,
fail_consent_updates: bool,
privacy_options: i32,
privacy_options_presentations: u32,
consent_debug_geography: i32,
reset_consent: bool,
loaded: HashSet<i32>,
events: VecDeque<AdEvent>,
}
impl Fake {
fn env_formats(key: &str) -> HashSet<i32> {
std::env::var(key)
.unwrap_or_default()
.split(',')
.filter_map(AdFormat::from_token)
.map(AdFormat::as_i32)
.collect()
}
fn push(&mut self, ev: AdEvent) {
self.events.push_back(ev);
}
}
static FAKE: LazyLock<Mutex<Fake>> = LazyLock::new(|| Mutex::new(Fake::default()));
fn lock() -> std::sync::MutexGuard<'static, Fake> {
FAKE.lock().unwrap_or_else(|p| p.into_inner())
}
fn event(format: i32, kind: &str) -> AdEvent {
AdEvent {
format,
kind: kind.to_string(),
error: String::new(),
reward_amount: 0,
reward_type: String::new(),
}
}
fn consent_update_failure() -> AdEvent {
let mut ev = event(-1, "consent_update_failed");
ev.error = "fake consent info update failure".into();
ev
}
pub unsafe fn admob_init_with_ump_test(
_test_devices: *const c_char,
use_test_ads: i32,
consent_debug_geography: i32,
reset_consent: i32,
) {
let mut f = lock();
f.use_test_ads = use_test_ads != 0;
f.consent_debug_geography = consent_debug_geography;
f.reset_consent = reset_consent != 0;
f.consent = match std::env::var("BEVY_ADMOB_FAKE_CONSENT")
.unwrap_or_default()
.to_ascii_lowercase()
.as_str()
{
"unknown" => 0,
"required" => 1,
"not_required" | "not-required" => 2,
_ => 3,
};
f.can_request_ads = std::env::var("BEVY_ADMOB_FAKE_CAN_REQUEST_ADS")
.map(|value| matches!(value.to_ascii_lowercase().as_str(), "1" | "true" | "yes"))
.unwrap_or(matches!(f.consent, 2 | 3));
let update_failure = std::env::var("BEVY_ADMOB_FAKE_CONSENT_UPDATE")
.unwrap_or_default()
.to_ascii_lowercase();
f.fail_consent_updates = update_failure == "failed";
if matches!(update_failure.as_str(), "failed" | "fail_once") {
f.push(consent_update_failure());
}
f.privacy_options = if std::env::var("BEVY_ADMOB_FAKE_PRIVACY_OPTIONS")
.map(|v| v.eq_ignore_ascii_case("required"))
.unwrap_or(false)
{
1 } else {
2 };
}
pub unsafe fn admob_load(format: i32, _unit_id: *const c_char) {
let no_fill = Fake::env_formats("BEVY_ADMOB_FAKE_NO_FILL");
let mut f = lock();
if no_fill.contains(&format) {
f.loaded.remove(&format);
let mut ev = event(format, "load_failed");
ev.error = "fake no fill".into();
f.push(ev);
} else {
f.loaded.insert(format);
f.push(event(format, "loaded"));
}
}
pub unsafe fn admob_show(format: i32) {
let show_fail = Fake::env_formats("BEVY_ADMOB_FAKE_SHOW_FAIL");
let reward_amount: i64 = std::env::var("BEVY_ADMOB_FAKE_REWARD_AMOUNT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(1);
let reward_type =
std::env::var("BEVY_ADMOB_FAKE_REWARD_TYPE").unwrap_or_else(|_| "Reward".into());
let mut f = lock();
if show_fail.contains(&format) || !f.loaded.contains(&format) {
f.loaded.remove(&format);
let mut ev = event(format, "show_failed");
ev.error = if show_fail.contains(&format) {
"fake show failure".into()
} else {
"ad not loaded".into()
};
f.push(ev);
return;
}
f.loaded.remove(&format);
f.push(event(format, "shown"));
if matches!(
AdFormat::from_i32(format),
Some(AdFormat::Rewarded | AdFormat::RewardedInterstitial)
) {
let mut ev = event(format, "reward");
ev.reward_amount = reward_amount;
ev.reward_type = reward_type;
f.push(ev);
}
f.push(event(format, "dismissed"));
}
pub unsafe fn admob_banner_show(_unit_id: *const c_char, _position: i32) {
lock().push(event(AdFormat::Banner.as_i32(), "shown"));
}
pub unsafe fn admob_banner_hide() {
lock().push(event(AdFormat::Banner.as_i32(), "dismissed"));
}
pub unsafe fn admob_request_consent() {
let mut f = lock();
if f.fail_consent_updates {
f.push(consent_update_failure());
return;
}
f.consent = 3;
f.can_request_ads = true;
}
pub unsafe fn admob_present_privacy_options() {
lock().privacy_options_presentations += 1;
}
pub unsafe fn admob_consent_status() -> i32 {
lock().consent
}
pub unsafe fn admob_can_request_ads() -> i32 {
lock().can_request_ads as i32
}
pub unsafe fn admob_privacy_options_requirement_status() -> i32 {
lock().privacy_options
}
pub unsafe fn admob_drain_events() -> *const c_char {
let events: Vec<AdEvent> = {
let mut f = lock();
f.events.drain(..).collect()
};
let json = serde_json::to_string(&events).unwrap_or_else(|_| "[]".into());
EVENTS_BUF.with(|buf| {
*buf.borrow_mut() = CString::new(json).unwrap_or_default();
buf.borrow().as_ptr()
})
}
thread_local! {
static EVENTS_BUF: std::cell::RefCell<CString> = std::cell::RefCell::new(CString::default());
}
#[cfg(test)]
pub fn reset() {
*lock() = Fake::default();
}
#[cfg(test)]
pub fn privacy_options_presentations() -> u32 {
lock().privacy_options_presentations
}
#[cfg(test)]
pub fn ump_test_config() -> (i32, bool) {
let fake = lock();
(fake.consent_debug_geography, fake.reset_consent)
}