use crate::{convert::*, jni_clear_ex, jni_with_env, loader::*, proxy::*, JObjectAutoLocal};
use jni::{
errors::Error,
objects::{GlobalRef, JMethodID, JObject},
signature::ReturnType,
JNIEnv,
};
#[derive(Debug)]
pub struct BroadcastReceiver {
receiver: GlobalRef,
proxy: Option<JniProxy>, forget: bool,
}
impl AsRef<JObject<'static>> for BroadcastReceiver {
fn as_ref(&self) -> &JObject<'static> {
self.receiver.as_obj()
}
}
impl std::ops::Deref for BroadcastReceiver {
type Target = JObject<'static>;
fn deref(&self) -> &Self::Target {
self.receiver.as_obj()
}
}
impl Drop for BroadcastReceiver {
fn drop(&mut self) {
if !self.forget {
let _ = jni_with_env(|env| {
self.unregister_inner(env)
.map_err(crate::jni_clear_ex_ignore)
});
}
}
}
impl BroadcastReceiver {
pub fn build(
handler: impl for<'a> Fn(&mut JNIEnv<'a>, &JObject<'a>, &JObject<'a>) -> Result<(), Error>
+ Send
+ Sync
+ 'static,
) -> Result<Self, Error> {
jni_with_env(|env| {
let loader = get_helper_class_loader()?;
let cls_rec = loader.load_class("rust/jniminhelper/BroadcastRec")?;
let cls_rec_hdl =
loader.load_class("rust/jniminhelper/BroadcastRec$BroadcastRecHdl")?;
let proxy = JniProxy::build(
env,
Some(loader),
[cls_rec_hdl.as_class()],
move |env, method, args| {
if method.get_method_name(env)? == "onReceive" && args.len() == 2 {
let _ = handler(env, args[0], args[1]).map_err(crate::jni_clear_ex_silent);
let _ = env.exception_clear();
}
JniProxy::void(env)
},
)?;
let receiver = env
.new_object(
cls_rec.as_class(),
"(Lrust/jniminhelper/BroadcastRec$BroadcastRecHdl;)V",
&[(&proxy).into()],
)
.global_ref(env)?;
Ok(Self {
receiver,
proxy: Some(proxy),
forget: false,
})
})
}
pub fn register(&self, intent_filter: &JObject<'_>) -> Result<(), Error> {
jni_with_env(|env| {
let context = android_context();
env.call_method(
context,
"registerReceiver",
"(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;",
&[(&self.receiver).into(), (&intent_filter).into()]
)
.clear_ex()
})
}
pub fn register_for_action(&self, action: &str) -> Result<(), Error> {
jni_with_env(|env| {
let action = action.new_jobject(env)?;
let filter = env
.new_object(
"android/content/IntentFilter",
"(Ljava/lang/String;)V",
&[(&action).into()],
)
.auto_local(env)?;
self.register(&filter)
})
}
#[inline(always)]
pub fn unregister(&self) -> Result<(), Error> {
jni_with_env(|env| self.unregister_inner(env))
}
fn unregister_inner(&self, env: &mut JNIEnv<'_>) -> Result<(), Error> {
let context = android_context();
env.call_method(
context,
"unregisterReceiver",
"(Landroid/content/BroadcastReceiver;)V",
&[(&self.receiver).into()],
)
.map(|_| ())
}
#[inline]
pub fn get_intent_action<'a>(
intent: impl AsRef<JObject<'a>>,
env: &mut JNIEnv<'_>,
) -> Result<String, Error> {
use std::sync::OnceLock;
static STORE: OnceLock<(GlobalRef, JMethodID)> = OnceLock::new();
if STORE.get().is_none() {
let class_intent = env.find_class("android/content/Intent").global_ref(env)?;
let method_get_action = env
.get_method_id(&class_intent, "getAction", "()Ljava/lang/String;")
.map_err(jni_clear_ex)?;
let _ = STORE.set((class_intent, method_get_action));
}
let store = STORE.get().unwrap();
let (class, method) = (store.0.as_class(), &store.1);
intent.class_check(class, env)?;
unsafe { env.call_method_unchecked(intent, method, ReturnType::Object, &[]) }
.get_object(env)?
.get_string(env)
}
pub fn forget(mut self) -> GlobalRef {
self.forget = true;
self.proxy.take().unwrap().forget();
self.receiver.clone()
}
}
#[cfg(feature = "futures")]
pub use waiter::*;
#[cfg(feature = "futures")]
mod waiter {
use super::*;
use futures_lite::StreamExt;
use std::{
collections::VecDeque,
pin::Pin,
sync::{Arc, Mutex},
task,
time::Duration,
};
#[derive(Debug)]
pub struct BroadcastWaiter {
receiver: BroadcastReceiver,
inner: Arc<BroadcastWaiterInner>,
}
#[derive(Debug)]
struct BroadcastWaiterInner {
waker: atomic_waker::AtomicWaker,
intents: Mutex<VecDeque<GlobalRef>>,
}
impl BroadcastWaiter {
pub fn build(
actions: impl IntoIterator<Item = impl AsRef<str>>,
) -> Result<Self, jni::errors::Error> {
let inner = Arc::new(BroadcastWaiterInner {
waker: atomic_waker::AtomicWaker::new(),
intents: Mutex::new(VecDeque::new()),
});
let inner_weak = Arc::downgrade(&inner);
let receiver = BroadcastReceiver::build(move |env, _, intent| {
if intent.is_null() {
return Ok(());
}
let Some(inner) = inner_weak.upgrade() else {
return Ok(());
};
let intent = env.new_global_ref(intent).map_err(jni_clear_ex)?;
inner.intents.lock().unwrap().push_back(intent);
inner.waker.wake();
Ok(())
})?;
for action in actions {
receiver.register_for_action(action.as_ref())?;
}
Ok(Self { receiver, inner })
}
pub fn receiver(&self) -> &BroadcastReceiver {
&self.receiver
}
pub fn count_received(&self) -> usize {
self.inner.intents.lock().unwrap().len()
}
pub fn take_next(&self) -> Option<GlobalRef> {
self.inner.intents.lock().unwrap().pop_front()
}
pub fn wait_timeout(&mut self, timeout: Duration) -> Option<GlobalRef> {
let fut = BroadcastWaiterFuture { waiter: self };
block_for_timeout(fut, timeout).unwrap_or(None)
}
#[inline(always)]
pub fn get_intent_action<'a>(
intent: impl AsRef<JObject<'a>>,
env: &mut JNIEnv<'_>,
) -> Result<String, Error> {
BroadcastReceiver::get_intent_action(intent, env)
}
}
pub fn block_for_timeout<T>(
fut: impl std::future::Future<Output = T>,
dur: std::time::Duration,
) -> Option<T> {
use futures_lite::{future::block_on, FutureExt};
let fut_comp = async { Some(fut.await) };
let fut_cancel = async {
futures_timer::Delay::new(dur).await;
None
};
block_on(fut_comp.or(fut_cancel))
}
impl futures_core::Stream for BroadcastWaiter {
type Item = GlobalRef;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
) -> task::Poll<Option<Self::Item>> {
if let Some(intent) = self.take_next() {
return task::Poll::Ready(Some(intent));
}
self.inner.waker.register(cx.waker());
if let Some(intent) = self.take_next() {
task::Poll::Ready(Some(intent))
} else {
task::Poll::Pending
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.count_received(), None)
}
}
struct BroadcastWaiterFuture<'a> {
waiter: &'a mut BroadcastWaiter,
}
impl<'a> std::future::Future for BroadcastWaiterFuture<'a> {
type Output = Option<GlobalRef>;
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
if let task::Poll::Ready(intent) = self.waiter.poll_next(cx) {
task::Poll::Ready(intent)
} else {
task::Poll::Pending
}
}
}
}