android_wakelock/lib.rs
1//! Safe and ergonomic Rust bindings to the [Android WakeLock
2//! API](https://developer.android.com/reference/android/os/PowerManager#newWakeLock(int,%20java.lang.String)).
3//! Wake locks allow an app or service to keep an Android device's display or
4//! processor awake in order to complete some work. For more information about
5//! wake locks, see the official [Android
6//! guide](https://developer.android.com/training/scheduling/wakelock).
7//!
8//! In short: **device battery life may be significantly affected by the use of
9//! this API**. Do not acquire `WakeLock`s unless you really need them, use the
10//! minimum levels possible, and be sure to release them as soon as possible.
11//!
12//! # Platform support
13//!
14//! This library should work with all Android API levels. It cannot be used on any
15//! other operating system, of course.
16//!
17//! # Creating wake locks
18//!
19//! The simplest way to create a wake lock is to use the [`partial`] function,
20//! which creates a [partial][`Level::Partial`] wake lock configured with
21//! reasonable defaults. This is the lowest level of wake lock, and is the most
22//! friendly to battery life while still keeping the device awake to perform
23//! computation.
24//!
25//! If you want to create a wake lock with a different level or with different
26//! flags, you can use [`WakeLock::builder`] to create a [`Builder`] that
27//! provides methods for setting other supported wake lock options.
28//!
29//! Creating a wake lock from Rust is a somewhat expensive operation, so it is
30//! better to create your wake locks up front and reuse them during your app's
31//! runtime as needed instead of creating them on-demand.
32//!
33//! # Acquiring and releasing wake locks
34//!
35//! Wake locks remain dormant until they are acquired. To acquire a wake lock,
36//! call [`acquire`][WakeLock::acquire] on the wake lock. This will return a
37//! guard object that will keep the wake lock acquired until it is dropped:
38//!
39//! ```no_run
40//! // Create the wake lock.
41//! let wake_lock = android_wakelock::partial("myapp:mytag")?;
42//!
43//! // Start keeping the device awake.
44//! let guard = wake_lock.acquire()?;
45//!
46//! // Do some work while the device is awake...
47//!
48//! // Release the wake lock to allow the device to sleep again.
49//! drop(guard);
50//!
51//! # Ok::<(), Box<dyn std::error::Error>>(())
52//! ```
53//!
54//! Multiple threads can share the same wake lock and acquire it concurrently. As
55//! long as at least one thread has acquired the wake lock, the device will be
56//! kept awake.
57//!
58//! ```no_run
59//! use std::{sync::Arc, thread};
60//!
61//! // Create the wake lock.
62//! let wake_lock = Arc::new(android_wakelock::partial("myapp:mytag")?);
63//! let wake_lock_clone = wake_lock.clone();
64//!
65//! // Spawn multiple threads that use the same wake lock to keep the device awake
66//! // while they do some work.
67//! let worker1 = thread::spawn(move || {
68//! // Keep the device awake while this worker runs.
69//! let _guard = wake_lock_clone.acquire().unwrap();
70//!
71//! // Do some work...
72//! });
73//! let worker2 = thread::spawn(move || {
74//! // Keep the device awake while this worker runs.
75//! let _guard = wake_lock.acquire().unwrap();
76//!
77//! // Some more work...
78//! });
79//!
80//! worker1.join().unwrap();
81//! worker2.join().unwrap();
82//!
83//! # Ok::<(), Box<dyn std::error::Error>>(())
84//! ```
85
86#![warn(
87 future_incompatible,
88 missing_debug_implementations,
89 missing_docs,
90 unreachable_pub,
91 unused,
92 clippy::all
93)]
94
95use std::fmt;
96
97use jni::{
98 objects::{GlobalRef, JObject, JValue},
99 AttachGuard, JavaVM,
100};
101
102const ACQUIRE_CAUSES_WAKEUP: i32 = 0x10000000;
103const ON_AFTER_RELEASE: i32 = 0x20000000;
104
105/// An error returned by the wake lock API. A variety of errors can occur when
106/// calling Android APIs, such as JNI errors, or exceptions actually thrown by the
107/// API itself.
108pub type Error = Box<dyn std::error::Error>;
109
110type Result<T> = std::result::Result<T, Error>;
111
112/// Create a new partial wake lock with the given tag.
113///
114/// This convenience function is equivalent to the following:
115///
116/// ```no_run
117/// use android_wakelock::{Level, WakeLock};
118///
119/// # let tag = "myapp:mytag";
120/// WakeLock::builder(tag)
121/// .level(Level::Partial)
122/// .build();
123/// ```
124pub fn partial<T: Into<String>>(tag: T) -> Result<WakeLock> {
125 WakeLock::builder(tag).build()
126}
127
128/// Possible levels for a wake lock.
129#[repr(i32)]
130#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
131pub enum Level {
132 /// Ensures that the CPU is running; the screen and keyboard backlight will
133 /// be allowed to go off.
134 ///
135 /// If the user presses the power button, then the screen will be turned off
136 /// but the CPU will be kept on until all partial wake locks have been
137 /// released.
138 Partial = 0x00000001,
139
140 /// Ensures that the screen and keyboard backlight are on at full
141 /// brightness.
142 ///
143 /// If the user presses the power button, then the wake lock will be
144 /// implicitly released by the system, causing both the screen and the CPU
145 /// to be turned off. Contrast with [`Level::Partial`].
146 ///
147 /// # Deprecation
148 ///
149 /// **This constant was deprecated in API level 17.** Most applications
150 /// should use `WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON` instead of
151 /// this type of wake lock, as it will be correctly managed by the platform
152 /// as the user moves between applications and doesn't require a special
153 /// permission.
154 #[deprecated]
155 Full = 0x0000001a,
156
157 /// Ensures that the screen is on at full brightness; the keyboard backlight
158 /// will be allowed to go off.
159 ///
160 /// If the user presses the power button, then the wake lock will be
161 /// implicitly released by the system, causing both the screen and the CPU
162 /// to be turned off. Contrast with [`Level::Partial`].
163 ///
164 /// # Deprecation
165 ///
166 /// **This constant was deprecated in API level 15.** Most applications
167 /// should use `WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON` instead of
168 /// this type of wake lock, as it will be correctly managed by the platform
169 /// as the user moves between applications and doesn't require a special
170 /// permission.
171 #[deprecated]
172 ScreenBright = 0x0000000a,
173
174 /// Wake lock level: Ensures that the screen is on (but may be dimmed); the
175 /// keyboard backlight will be allowed to go off.
176 ///
177 /// If the user presses the power button, then the wake lock will be
178 /// implicitly released by the system, causing both the screen and the CPU
179 /// to be turned off. Contrast with [`Level::Partial`].
180 ///
181 /// # Deprecation
182 ///
183 /// **This constant was deprecated in API level 17.** Most applications
184 /// should use `WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON` instead of
185 /// this type of wake lock, as it will be correctly managed by the platform
186 /// as the user moves between applications and doesn't require a special
187 /// permission.
188 #[deprecated]
189 ScreenDim = 0x00000006,
190}
191
192/// A builder for configuring and creating a wake lock.
193#[derive(Clone, Debug)]
194pub struct Builder {
195 tag: String,
196 level: Level,
197 acquire_causes_wakeup: bool,
198 on_after_release: bool,
199}
200
201impl Builder {
202 /// Set the wake lock level.
203 ///
204 /// Generally [`Level::Partial`] wake locks are preferred, and is the
205 /// default level if not specified. See [`Level`] for more information about
206 /// the different available wake lock levels.
207 pub fn level(mut self, level: Level) -> Self {
208 self.level = level;
209 self
210 }
211
212 /// Turn the screen on when the wake lock is acquired.
213 ///
214 /// This flag requires `Manifest.permission.TURN_SCREEN_ON` for apps
215 /// targeting Android version `Build.VERSION_CODES#UPSIDE_DOWN_CAKE` and
216 /// higher.
217 ///
218 /// Normally wake locks don't actually wake the device, they just cause the
219 /// screen to remain on once it's already on. This flag will cause the
220 /// device to wake up when the wake lock is acquired.
221 ///
222 /// Android TV playback devices attempt to turn on the HDMI-connected TV via
223 /// HDMI-CEC on any wake-up, including wake-ups triggered by wake locks.
224 ///
225 /// Cannot be used with [`Level::Partial`].
226 ///
227 /// # Deprecation
228 ///
229 /// **This option was deprecated in API level 33.** Most applications should
230 /// use `R.attr.turnScreenOn` or `Activity.setTurnScreenOn(boolean)`
231 /// instead, as this prevents the previous foreground app from being resumed
232 /// first when the screen turns on.
233 #[deprecated]
234 pub fn acquire_causes_wakeup(mut self, acquire_causes_wakeup: bool) -> Self {
235 self.acquire_causes_wakeup = acquire_causes_wakeup;
236 self
237 }
238
239 /// When this wake lock is released, poke the user activity timer so the
240 /// screen stays on for a little longer.
241 ///
242 /// This will not turn the screen on if it is not already on.
243 ///
244 /// Cannot be used with [`Level::Partial`].
245 pub fn on_after_release(mut self, on_after_release: bool) -> Self {
246 self.on_after_release = on_after_release;
247 self
248 }
249
250 /// Creates a new wake lock with the specified level and options.
251 pub fn build(&self) -> Result<WakeLock> {
252 let ctx = ndk_context::android_context();
253 let vm = unsafe { JavaVM::from_raw(ctx.vm().cast()) }?;
254 let mut env = vm.attach_current_thread()?;
255
256 // Fetch the PowerManager system service.
257 let power_manager_service_id = env.new_string("power")?;
258 let power_manager = catch_exceptions(&mut env, |env| {
259 env.call_method(
260 unsafe { JObject::from_raw(ctx.context().cast()) },
261 "getSystemService",
262 "(Ljava/lang/String;)Ljava/lang/Object;",
263 &[JValue::from(&power_manager_service_id)],
264 )?
265 .l()
266 })?;
267
268 let name = env.new_string(&self.tag)?;
269 let mut flags = self.level as i32;
270
271 if self.acquire_causes_wakeup {
272 flags |= ACQUIRE_CAUSES_WAKEUP;
273 }
274
275 if self.on_after_release {
276 flags |= ON_AFTER_RELEASE;
277 }
278
279 // Create the wake lock.
280 let result = catch_exceptions(&mut env, |env| {
281 env.call_method(
282 &power_manager,
283 "newWakeLock",
284 "(ILjava/lang/String;)Landroid/os/PowerManager$WakeLock;",
285 &[JValue::from(flags), JValue::from(&name)],
286 )
287 })?;
288
289 let wake_lock = env.new_global_ref(result.l()?)?;
290
291 drop(env);
292
293 Ok(WakeLock {
294 wake_lock,
295 vm,
296 tag: self.tag.clone(),
297 })
298 }
299}
300
301/// A wake lock is a mechanism to indicate that your application needs to have
302/// the device stay on.
303///
304/// To obtain a wake lock, you can use [`WakeLock::builder`] to configure and
305/// create a wake lock, or you can use [`partial`] to create a partial wake
306/// lock configured with reasonable defaults.
307///
308/// Any application using a `WakeLock` must request the
309/// `android.permission.WAKE_LOCK` permission in an `<uses-permission>` element
310/// of the application's manifest.
311#[derive(Debug)]
312pub struct WakeLock {
313 /// Reference to the underlying Java object.
314 wake_lock: GlobalRef,
315
316 /// The JVM the object belongs to.
317 vm: JavaVM,
318
319 /// The tag specified when the wake lock was created.
320 tag: String,
321}
322
323impl WakeLock {
324 /// Create a new builder with the given tag for configuring and creating a
325 /// wake lock.
326 ///
327 /// # Tags
328 ///
329 /// Your class name (or other tag) for debugging purposes. Recommended
330 /// naming conventions for tags to make debugging easier:
331 ///
332 /// - use a unique prefix delimited by a colon for your app/library (e.g.
333 /// `gmail:mytag`) to make it easier to understand where the wake locks
334 /// comes from. This namespace will also avoid collision for tags inside
335 /// your app coming from different libraries which will make debugging
336 /// easier.
337 /// - use constants (e.g. do not include timestamps in the tag) to make it
338 /// easier for tools to aggregate similar wake locks. When collecting
339 /// debugging data, the platform only monitors a finite number of tags,
340 /// using constants will help tools to provide better debugging data.
341 /// - avoid wrapping the tag or a prefix to avoid collision with wake lock
342 /// tags from the platform (e.g. `*alarm*`).
343 /// - never include personally identifiable information for privacy reasons.
344 pub fn builder<T: Into<String>>(tag: T) -> Builder {
345 Builder {
346 tag: tag.into(),
347 level: Level::Partial,
348 acquire_causes_wakeup: false,
349 on_after_release: false,
350 }
351 }
352
353 /// Returns true if the wake lock has outstanding references not yet
354 /// released.
355 pub fn is_held(&self) -> Result<bool> {
356 let mut env = self.vm.attach_current_thread()?;
357
358 catch_exceptions(&mut env, |env| {
359 env.call_method(&self.wake_lock, "isHeld", "()Z", &[])?.z()
360 })
361 }
362
363 /// Acquire the wake lock and force the device to stay on at the level that
364 /// was requested when the wake lock was created.
365 ///
366 /// Returns a [`Guard`] which can be used to release the lock. You should
367 /// release wake locks when you are done and don't need the lock anymore. It
368 /// is very important to do this as soon as possible to avoid running down
369 /// the device's battery excessively.
370 ///
371 /// Wake locks are reference counted like a semaphore and may be acquired
372 /// multiple times by the same or a different thread. The wake lock is not
373 /// released on the device until all acquired references have been released.
374 ///
375 /// # Examples
376 ///
377 /// ```no_run
378 /// // Create the wake lock.
379 /// let wake_lock = android_wakelock::partial("myapp:mytag")?;
380 ///
381 /// // Start keeping the device awake.
382 /// let guard = wake_lock.acquire()?;
383 ///
384 /// // Do some work while the device is awake...
385 ///
386 /// // Release the wake lock to allow the device to sleep again.
387 /// drop(guard);
388 ///
389 /// # Ok::<(), Box<dyn std::error::Error>>(())
390 /// ```
391 pub fn acquire(&self) -> Result<Guard<'_>> {
392 let mut env = self.vm.attach_current_thread()?;
393
394 catch_exceptions(&mut env, |env| {
395 env.call_method(&self.wake_lock, "acquire", "()V", &[])
396 })?;
397
398 log::debug!("acquired wake lock \"{}\"", self.tag);
399
400 Ok(Guard {
401 wake_lock: self.wake_lock.clone(),
402 env,
403 tag: &self.tag,
404 })
405 }
406}
407
408/// A guard for an acquired wake lock.
409///
410/// To create a guard see [`WakeLock::acquire`].
411///
412/// The wake lock is released automatically when the guard is dropped, but
413/// panics if there is an error releasing the wake lock. If you want to handle
414/// errors on release then you can call [`Guard::release`] instead.
415///
416/// The current thread will remain attached to the current JVM until the guard
417/// is released. The guard cannot be sent between threads.
418pub struct Guard<'a> {
419 /// Reference to the underlying Java object.
420 wake_lock: GlobalRef,
421
422 env: AttachGuard<'a>,
423
424 /// The tag specified when the wake lock was created.
425 tag: &'a str,
426}
427
428impl Guard<'_> {
429 /// Releases the wake lock, returning an error if the underlying API threw
430 /// an exception.
431 pub fn release(mut self) -> Result<()> {
432 self.release_one()
433 }
434
435 fn release_one(&mut self) -> Result<()> {
436 catch_exceptions(&mut self.env, |env| {
437 env.call_method(&self.wake_lock, "release", "()V", &[])?;
438
439 log::debug!("released wake lock \"{}\"", self.tag);
440
441 Ok(())
442 })
443 }
444}
445
446impl fmt::Debug for Guard<'_> {
447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448 f.debug_struct("Guard")
449 .field("wake_lock", &self.wake_lock)
450 .field("tag", &self.tag)
451 .finish()
452 }
453}
454
455impl Drop for Guard<'_> {
456 fn drop(&mut self) {
457 if let Err(e) = self.release_one() {
458 panic!("error releasing wake lock \"{}\" on drop: {}", self.tag, e);
459 }
460 }
461}
462
463/// Helper for handling Java exceptions thrown when entering Java code that turns
464/// thrown exceptions into formatted Rust errors.
465#[inline]
466fn catch_exceptions<'a, T, F>(env: &mut jni::JNIEnv<'a>, f: F) -> Result<T>
467where
468 F: FnOnce(&mut jni::JNIEnv<'a>) -> jni::errors::Result<T>,
469{
470 match f(env) {
471 Ok(value) => Ok(value),
472 Err(e @ jni::errors::Error::JavaException) => Err({
473 if let Ok(exception) = env.exception_occurred() {
474 let _ = env.exception_clear();
475
476 env.call_method(exception, "getMessage", "()Ljava/lang/String;", &[])
477 .and_then(|value| value.l())
478 .and_then(|message| {
479 env.get_string(&message.into())
480 .map(|s| s.to_string_lossy().into_owned())
481 })
482 .map(|message| message.into())
483 .unwrap_or_else(|_| e.into())
484 } else {
485 e.into()
486 }
487 }),
488 Err(e) => Err(e.into()),
489 }
490}