#![warn(
future_incompatible,
missing_debug_implementations,
missing_docs,
unreachable_pub,
unused,
clippy::all
)]
use std::fmt;
use jni::{
objects::{GlobalRef, JObject, JValue},
AttachGuard, JavaVM,
};
const ACQUIRE_CAUSES_WAKEUP: i32 = 0x10000000;
const ON_AFTER_RELEASE: i32 = 0x20000000;
pub type Error = Box<dyn std::error::Error>;
type Result<T> = std::result::Result<T, Error>;
pub fn partial<T: Into<String>>(tag: T) -> Result<WakeLock> {
WakeLock::builder(tag).build()
}
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Level {
Partial = 0x00000001,
#[deprecated]
Full = 0x0000001a,
#[deprecated]
ScreenBright = 0x0000000a,
#[deprecated]
ScreenDim = 0x00000006,
}
#[derive(Clone, Debug)]
pub struct Builder {
tag: String,
level: Level,
acquire_causes_wakeup: bool,
on_after_release: bool,
}
impl Builder {
pub fn level(mut self, level: Level) -> Self {
self.level = level;
self
}
#[deprecated]
pub fn acquire_causes_wakeup(mut self, acquire_causes_wakeup: bool) -> Self {
self.acquire_causes_wakeup = acquire_causes_wakeup;
self
}
pub fn on_after_release(mut self, on_after_release: bool) -> Self {
self.on_after_release = on_after_release;
self
}
pub fn build(&self) -> Result<WakeLock> {
let ctx = ndk_context::android_context();
let vm = unsafe { JavaVM::from_raw(ctx.vm().cast()) }?;
let mut env = vm.attach_current_thread()?;
let power_manager_service_id = env.new_string("power")?;
let power_manager = catch_exceptions(&mut env, |env| {
env.call_method(
unsafe { JObject::from_raw(ctx.context().cast()) },
"getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;",
&[JValue::from(&power_manager_service_id)],
)?
.l()
})?;
let name = env.new_string(&self.tag)?;
let mut flags = self.level as i32;
if self.acquire_causes_wakeup {
flags |= ACQUIRE_CAUSES_WAKEUP;
}
if self.on_after_release {
flags |= ON_AFTER_RELEASE;
}
let result = catch_exceptions(&mut env, |env| {
env.call_method(
&power_manager,
"newWakeLock",
"(ILjava/lang/String;)Landroid/os/PowerManager$WakeLock;",
&[JValue::from(flags), JValue::from(&name)],
)
})?;
let wake_lock = env.new_global_ref(result.l()?)?;
drop(env);
Ok(WakeLock {
wake_lock,
vm,
tag: self.tag.clone(),
})
}
}
#[derive(Debug)]
pub struct WakeLock {
wake_lock: GlobalRef,
vm: JavaVM,
tag: String,
}
impl WakeLock {
pub fn builder<T: Into<String>>(tag: T) -> Builder {
Builder {
tag: tag.into(),
level: Level::Partial,
acquire_causes_wakeup: false,
on_after_release: false,
}
}
pub fn is_held(&self) -> Result<bool> {
let mut env = self.vm.attach_current_thread()?;
catch_exceptions(&mut env, |env| {
env.call_method(&self.wake_lock, "isHeld", "()Z", &[])?.z()
})
}
pub fn acquire(&self) -> Result<Guard<'_>> {
let mut env = self.vm.attach_current_thread()?;
catch_exceptions(&mut env, |env| {
env.call_method(&self.wake_lock, "acquire", "()V", &[])
})?;
log::debug!("acquired wake lock \"{}\"", self.tag);
Ok(Guard {
wake_lock: self.wake_lock.clone(),
env,
tag: &self.tag,
})
}
}
pub struct Guard<'a> {
wake_lock: GlobalRef,
env: AttachGuard<'a>,
tag: &'a str,
}
impl Guard<'_> {
pub fn release(mut self) -> Result<()> {
self.release_one()
}
fn release_one(&mut self) -> Result<()> {
catch_exceptions(&mut self.env, |env| {
env.call_method(&self.wake_lock, "release", "()V", &[])?;
log::debug!("released wake lock \"{}\"", self.tag);
Ok(())
})
}
}
impl fmt::Debug for Guard<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Guard")
.field("wake_lock", &self.wake_lock)
.field("tag", &self.tag)
.finish()
}
}
impl Drop for Guard<'_> {
fn drop(&mut self) {
if let Err(e) = self.release_one() {
panic!("error releasing wake lock \"{}\" on drop: {}", self.tag, e);
}
}
}
#[inline]
fn catch_exceptions<'a, T, F>(env: &mut jni::JNIEnv<'a>, f: F) -> Result<T>
where
F: FnOnce(&mut jni::JNIEnv<'a>) -> jni::errors::Result<T>,
{
match f(env) {
Ok(value) => Ok(value),
Err(e @ jni::errors::Error::JavaException) => Err({
if let Ok(exception) = env.exception_occurred() {
let _ = env.exception_clear();
env.call_method(exception, "getMessage", "()Ljava/lang/String;", &[])
.and_then(|value| value.l())
.and_then(|message| {
env.get_string(&message.into())
.map(|s| s.to_string_lossy().into_owned())
})
.map(|message| message.into())
.unwrap_or_else(|_| e.into())
} else {
e.into()
}
}),
Err(e) => Err(e.into()),
}
}