ndk/looper.rs
1//! Bindings for [`ALooper`]
2//!
3//! In Android, [`ALooper`]s are inherently thread-local. Due to this, there are two different
4//! [`ALooper`] interfaces exposed in this module:
5//!
6//! * [`ThreadLooper`], which has methods for the operations performable with a looper in one's own
7//! thread; and
8//! * [`ForeignLooper`], which has methods for the operations performable with any thread's looper.
9//!
10//! [`ALooper`]: https://developer.android.com/ndk/reference/group/looper#alooper
11
12use std::mem::ManuallyDrop;
13use std::os::{
14 fd::{AsRawFd, BorrowedFd, RawFd},
15 raw::c_void,
16};
17use std::ptr;
18use std::time::Duration;
19use thiserror::Error;
20
21use crate::utils::abort_on_panic;
22
23/// A thread-local native [`ALooper *`]. This promises that there is a looper associated with the
24/// current thread.
25///
26/// [`ALooper *`]: https://developer.android.com/ndk/reference/group/looper#alooper
27#[derive(Debug)]
28pub struct ThreadLooper {
29 _marker: std::marker::PhantomData<*mut ()>, // Not send or sync
30 foreign: ForeignLooper,
31}
32
33bitflags::bitflags! {
34 /// Flags for file descriptor events that a looper can monitor.
35 ///
36 /// These flag bits can be combined to monitor multiple events at once.
37 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
38 pub struct FdEvent : u32 {
39 /// The file descriptor is available for read operations.
40 #[doc(alias = "ALOOPER_EVENT_INPUT")]
41 const INPUT = ffi::ALOOPER_EVENT_INPUT;
42 /// The file descriptor is available for write operations.
43 #[doc(alias = "ALOOPER_EVENT_OUTPUT")]
44 const OUTPUT = ffi::ALOOPER_EVENT_OUTPUT;
45 /// The file descriptor has encountered an error condition.
46 ///
47 /// The looper always sends notifications about errors; it is not necessary to specify this
48 /// event flag in the requested event set.
49 #[doc(alias = "ALOOPER_EVENT_ERROR")]
50 const ERROR = ffi::ALOOPER_EVENT_ERROR;
51 /// The file descriptor was hung up.
52 ///
53 /// For example, indicates that the remote end of a pipe or socket was closed.
54 ///
55 /// The looper always sends notifications about hangups; it is not necessary to specify this
56 /// event flag in the requested event set.
57 #[doc(alias = "ALOOPER_EVENT_HANGUP")]
58 const HANGUP = ffi::ALOOPER_EVENT_HANGUP;
59 /// The file descriptor is invalid.
60 ///
61 /// For example, the file descriptor was closed prematurely.
62 ///
63 /// The looper always sends notifications about invalid file descriptors; it is not
64 /// necessary to specify this event flag in the requested event set.
65 #[doc(alias = "ALOOPER_EVENT_INVALID")]
66 const INVALID = ffi::ALOOPER_EVENT_INVALID;
67
68 // https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags
69 const _ = !0;
70 }
71}
72
73/// The poll result from a [`ThreadLooper`].
74#[derive(Debug)]
75pub enum Poll<'fd> {
76 /// This looper was woken using [`ForeignLooper::wake()`]
77 Wake,
78 /// For [`ThreadLooper::poll_once*()`][ThreadLooper::poll_once()], an event was received and processed using a callback.
79 Callback,
80 /// For [`ThreadLooper::poll_*_timeout()`][ThreadLooper::poll_once_timeout()], the requested timeout was reached before any events.
81 Timeout,
82 /// An event was received
83 Event {
84 ident: i32,
85 /// # Safety
86 /// The caller should guarantee that this file descriptor remains open after it was added
87 /// via [`ForeignLooper::add_fd()`] or [`ForeignLooper::add_fd_with_callback()`].
88 fd: BorrowedFd<'fd>,
89 events: FdEvent,
90 data: *mut c_void,
91 },
92}
93
94#[derive(Debug, Copy, Clone, Error)]
95#[error("Android Looper error")]
96pub struct LooperError;
97
98impl ThreadLooper {
99 /// Prepares a looper for the current thread and returns it
100 pub fn prepare() -> Self {
101 unsafe {
102 let ptr = ffi::ALooper_prepare(ffi::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as _);
103 let foreign = ForeignLooper::from_ptr(ptr::NonNull::new(ptr).expect("looper non null"));
104 Self {
105 _marker: std::marker::PhantomData,
106 foreign,
107 }
108 }
109 }
110
111 /// Returns the looper associated with the current thread, if any.
112 pub fn for_thread() -> Option<Self> {
113 Some(Self {
114 _marker: std::marker::PhantomData,
115 foreign: ForeignLooper::for_thread()?,
116 })
117 }
118
119 /// Polls the looper, blocking on processing an event, but with a timeout in milliseconds.
120 /// Give a timeout of `0` to make this non-blocking.
121 fn poll_once_ms(&self, ms: i32) -> Result<Poll<'_>, LooperError> {
122 let mut fd = -1;
123 let mut events = -1;
124 let mut data: *mut c_void = ptr::null_mut();
125 match unsafe { ffi::ALooper_pollOnce(ms, &mut fd, &mut events, &mut data) } {
126 ffi::ALOOPER_POLL_WAKE => Ok(Poll::Wake),
127 ffi::ALOOPER_POLL_CALLBACK => Ok(Poll::Callback),
128 ffi::ALOOPER_POLL_TIMEOUT => Ok(Poll::Timeout),
129 ffi::ALOOPER_POLL_ERROR => Err(LooperError),
130 ident if ident >= 0 => Ok(Poll::Event {
131 ident,
132 // SAFETY: Even though this FD at least shouldn't outlive self, a user could have
133 // closed it after calling add_fd or add_fd_with_callback.
134 fd: unsafe { BorrowedFd::borrow_raw(fd) },
135 events: FdEvent::from_bits(events as u32)
136 .expect("poll event contains unknown bits"),
137 data,
138 }),
139 _ => unreachable!(),
140 }
141 }
142
143 /// Polls the looper, blocking on processing an event.
144 #[inline]
145 pub fn poll_once(&self) -> Result<Poll<'_>, LooperError> {
146 self.poll_once_ms(-1)
147 }
148
149 /// Polls the looper, blocking on processing an event, but with a timeout. Give a timeout of
150 /// [`Duration::ZERO`] to make this non-blocking.
151 ///
152 /// It panics if the timeout is larger than expressible as an [`i32`] of milliseconds (roughly 25
153 /// days).
154 #[inline]
155 pub fn poll_once_timeout(&self, timeout: Duration) -> Result<Poll<'_>, LooperError> {
156 self.poll_once_ms(
157 timeout
158 .as_millis()
159 .try_into()
160 .expect("Supplied timeout is too large"),
161 )
162 }
163
164 /// Repeatedly polls the looper, blocking on processing an event, but with a timeout in
165 /// milliseconds. Give a timeout of `0` to make this non-blocking.
166 ///
167 /// This function will never return [`Poll::Callback`].
168 fn poll_all_ms(&self, ms: i32) -> Result<Poll<'_>, LooperError> {
169 let mut fd = -1;
170 let mut events = -1;
171 let mut data: *mut c_void = ptr::null_mut();
172 match unsafe { ffi::ALooper_pollAll(ms, &mut fd, &mut events, &mut data) } {
173 ffi::ALOOPER_POLL_WAKE => Ok(Poll::Wake),
174 ffi::ALOOPER_POLL_TIMEOUT => Ok(Poll::Timeout),
175 ffi::ALOOPER_POLL_ERROR => Err(LooperError),
176 ident if ident >= 0 => Ok(Poll::Event {
177 ident,
178 // SAFETY: Even though this FD at least shouldn't outlive self, a user could have
179 // closed it after calling add_fd or add_fd_with_callback.
180 fd: unsafe { BorrowedFd::borrow_raw(fd) },
181 events: FdEvent::from_bits(events as u32)
182 .expect("poll event contains unknown bits"),
183 data,
184 }),
185 _ => unreachable!(),
186 }
187 }
188
189 /// Repeatedly polls the looper, blocking on processing an event.
190 ///
191 /// This function will never return [`Poll::Callback`].
192 #[inline]
193 pub fn poll_all(&self) -> Result<Poll<'_>, LooperError> {
194 self.poll_all_ms(-1)
195 }
196
197 /// Repeatedly polls the looper, blocking on processing an event, but with a timeout. Give a
198 /// timeout of [`Duration::ZERO`] to make this non-blocking.
199 ///
200 /// This function will never return [`Poll::Callback`].
201 ///
202 /// It panics if the timeout is larger than expressible as an [`i32`] of milliseconds (roughly 25
203 /// days).
204 #[inline]
205 pub fn poll_all_timeout(&self, timeout: Duration) -> Result<Poll<'_>, LooperError> {
206 self.poll_all_ms(
207 timeout
208 .as_millis()
209 .try_into()
210 .expect("Supplied timeout is too large"),
211 )
212 }
213
214 /// Adds a file descriptor to be polled, with a callback that is invoked when any of the
215 /// [`FdEvent`]s described in `events` is triggered.
216 ///
217 /// The callback receives the file descriptor it is associated with and a bitmask of the poll
218 /// events that were triggered (typically [`FdEvent::INPUT`]). It should return [`true`] to
219 /// continue receiving callbacks, or [`false`] to have the callback unregistered.
220 ///
221 /// See also [the NDK
222 /// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd).
223 ///
224 /// Note that this will leak a [`Box`] unless the callback returns [`false`] to unregister
225 /// itself.
226 ///
227 /// # Threading
228 /// This function will be called on the current thread when this [`ThreadLooper`] is
229 /// polled. A callback can also be registered from other threads via the equivalent
230 /// [`ForeignLooper::add_fd_with_callback()`] function, which requires a [`Send`] bound.
231 ///
232 /// # Safety
233 /// The caller should guarantee that this file descriptor stays open until it is removed via
234 /// [`remove_fd()`][ForeignLooper::remove_fd()] or by returning [`false`] from the callback,
235 /// and for however long the caller wishes to use this file descriptor inside and after the
236 /// callback.
237 #[doc(alias = "ALooper_addFd")]
238 pub fn add_fd_with_callback<F: FnMut(BorrowedFd<'_>, FdEvent) -> bool>(
239 &self,
240 fd: BorrowedFd<'_>,
241 events: FdEvent,
242 callback: F,
243 ) -> Result<(), LooperError> {
244 unsafe {
245 self.foreign
246 .add_fd_with_callback_assume_send(fd, events, callback)
247 }
248 }
249
250 /// Returns a reference to the [`ForeignLooper`] that is associated with the current thread.
251 pub fn as_foreign(&self) -> &ForeignLooper {
252 &self.foreign
253 }
254
255 pub fn into_foreign(self) -> ForeignLooper {
256 self.foreign
257 }
258}
259
260/// A native [`ALooper *`], not necessarily allocated with the current thread.
261///
262/// [`ALooper *`]: https://developer.android.com/ndk/reference/group/looper#alooper
263#[derive(Debug)]
264pub struct ForeignLooper {
265 ptr: ptr::NonNull<ffi::ALooper>,
266}
267
268unsafe impl Send for ForeignLooper {}
269unsafe impl Sync for ForeignLooper {}
270
271impl Drop for ForeignLooper {
272 fn drop(&mut self) {
273 unsafe { ffi::ALooper_release(self.ptr.as_ptr()) }
274 }
275}
276
277impl Clone for ForeignLooper {
278 fn clone(&self) -> Self {
279 unsafe {
280 ffi::ALooper_acquire(self.ptr.as_ptr());
281 Self { ptr: self.ptr }
282 }
283 }
284}
285
286impl ForeignLooper {
287 /// Returns the looper associated with the current thread, if any.
288 #[inline]
289 pub fn for_thread() -> Option<Self> {
290 ptr::NonNull::new(unsafe { ffi::ALooper_forThread() })
291 .map(|ptr| unsafe { Self::from_ptr(ptr) })
292 }
293
294 /// Construct a [`ForeignLooper`] object from the given pointer.
295 ///
296 /// # Safety
297 /// By calling this function, you guarantee that the pointer is a valid, non-null pointer to an
298 /// NDK [`ffi::ALooper`].
299 #[inline]
300 pub unsafe fn from_ptr(ptr: ptr::NonNull<ffi::ALooper>) -> Self {
301 ffi::ALooper_acquire(ptr.as_ptr());
302 Self { ptr }
303 }
304
305 /// Returns a pointer to the NDK `ALooper` object.
306 #[inline]
307 pub fn ptr(&self) -> ptr::NonNull<ffi::ALooper> {
308 self.ptr
309 }
310
311 /// Wakes the looper. An event of [`Poll::Wake`] will be sent.
312 pub fn wake(&self) {
313 unsafe { ffi::ALooper_wake(self.ptr.as_ptr()) }
314 }
315
316 /// Adds a file descriptor to be polled, without a callback.
317 ///
318 /// See also [the NDK
319 /// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd).
320 ///
321 /// # Safety
322 /// The caller should guarantee that this file descriptor stays open until it is removed via
323 /// [`remove_fd()`][Self::remove_fd()], and for however long the caller wishes to use this file
324 /// descriptor when it is returned in [`Poll::Event::fd`].
325
326 // `ALooper_addFd` won't dereference `data`; it will only pass it on to the event.
327 // Optionally dereferencing it there already enforces `unsafe` context.
328 #[allow(clippy::not_unsafe_ptr_arg_deref)]
329 pub fn add_fd(
330 &self,
331 fd: BorrowedFd<'_>,
332 ident: i32,
333 events: FdEvent,
334 data: *mut c_void,
335 ) -> Result<(), LooperError> {
336 match unsafe {
337 ffi::ALooper_addFd(
338 self.ptr.as_ptr(),
339 fd.as_raw_fd(),
340 ident,
341 events.bits() as i32,
342 None,
343 data,
344 )
345 } {
346 1 => Ok(()),
347 -1 => Err(LooperError),
348 _ => unreachable!(),
349 }
350 }
351
352 /// Adds a file descriptor to be polled, with a callback that is invoked when any of the
353 /// [`FdEvent`]s described in `events` is triggered.
354 ///
355 /// The callback receives the file descriptor it is associated with and a bitmask of the poll
356 /// events that were triggered (typically [`FdEvent::INPUT`]). It should return [`true`] to
357 /// continue receiving callbacks, or [`false`] to have the callback unregistered.
358 ///
359 /// See also [the NDK
360 /// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd).
361 ///
362 /// Note that this will leak a [`Box`] unless the callback returns [`false`] to unregister
363 /// itself.
364 ///
365 /// # Threading
366 /// This function will be called on the looper thread where and when it is polled.
367 /// For registering callbacks without [`Send`] requirement, call the equivalent
368 /// [`ThreadLooper::add_fd_with_callback()`] function on the Looper thread.
369 ///
370 /// # Safety
371 /// The caller should guarantee that this file descriptor stays open until it is removed via
372 /// [`remove_fd()`][Self::remove_fd()] or by returning [`false`] from the callback, and for
373 /// however long the caller wishes to use this file descriptor inside and after the callback.
374 #[doc(alias = "ALooper_addFd")]
375 pub fn add_fd_with_callback<F: FnMut(BorrowedFd<'_>, FdEvent) -> bool + Send>(
376 &self,
377 fd: BorrowedFd<'_>,
378 events: FdEvent,
379 callback: F,
380 ) -> Result<(), LooperError> {
381 unsafe { self.add_fd_with_callback_assume_send(fd, events, callback) }
382 }
383
384 /// Private helper to deduplicate/commonize the implementation behind
385 /// [`ForeignLooper::add_fd_with_callback()`] and [`ThreadLooper::add_fd_with_callback()`],
386 /// as both have their own way of guaranteeing thread-safety. The former, [`ForeignLooper`],
387 /// requires the closure to be [`Send`]. The latter, [`ThreadLooper`], can only exist on the
388 /// thread where polling happens and where the closure will end up being invoked, and does not
389 /// require [`Send`].
390 ///
391 /// # Safety
392 /// The caller must guarantee that `F` is [`Send`] or that `F` will only run on the current
393 /// thread. See the explanation above about why this function exists.
394 unsafe fn add_fd_with_callback_assume_send<F: FnMut(BorrowedFd<'_>, FdEvent) -> bool>(
395 &self,
396 fd: BorrowedFd<'_>,
397 events: FdEvent,
398 callback: F,
399 ) -> Result<(), LooperError> {
400 extern "C" fn cb_handler<F: FnMut(BorrowedFd<'_>, FdEvent) -> bool>(
401 fd: RawFd,
402 events: i32,
403 data: *mut c_void,
404 ) -> i32 {
405 abort_on_panic(|| unsafe {
406 let mut cb = ManuallyDrop::new(Box::<F>::from_raw(data as *mut _));
407 let events = FdEvent::from_bits_retain(
408 events.try_into().expect("Unexpected sign bit in `events`"),
409 );
410 let keep_registered = cb(BorrowedFd::borrow_raw(fd), events);
411 if !keep_registered {
412 ManuallyDrop::into_inner(cb);
413 }
414 keep_registered as i32
415 })
416 }
417 let data = Box::into_raw(Box::new(callback)) as *mut _;
418 match unsafe {
419 ffi::ALooper_addFd(
420 self.ptr.as_ptr(),
421 fd.as_raw_fd(),
422 ffi::ALOOPER_POLL_CALLBACK,
423 events.bits() as i32,
424 Some(cb_handler::<F>),
425 data,
426 )
427 } {
428 1 => Ok(()),
429 -1 => Err(LooperError),
430 _ => unreachable!(),
431 }
432 }
433
434 /// Removes a previously added file descriptor from the looper.
435 ///
436 /// Returns [`true`] if the file descriptor was removed, [`false`] if it was not previously
437 /// registered.
438 ///
439 /// # Safety
440 /// When this method returns, it is safe to close the file descriptor since the looper will no
441 /// longer have a reference to it. However, it is possible for the callback to already be
442 /// running or for it to run one last time if the file descriptor was already signalled.
443 /// Calling code is responsible for ensuring that this case is safely handled. For example, if
444 /// the callback takes care of removing itself during its own execution either by returning `0`
445 /// or by calling this method, then it can be guaranteed to not be invoked again at any later
446 /// time unless registered anew.
447 ///
448 /// Note that unregistering a file descriptor with callback will leak a [`Box`] created in
449 /// [`add_fd_with_callback()`][Self::add_fd_with_callback()]. Consider returning [`false`]
450 /// from the callback instead to drop it.
451 pub fn remove_fd(&self, fd: BorrowedFd<'_>) -> Result<bool, LooperError> {
452 match unsafe { ffi::ALooper_removeFd(self.ptr.as_ptr(), fd.as_raw_fd()) } {
453 1 => Ok(true),
454 0 => Ok(false),
455 -1 => Err(LooperError),
456 _ => unreachable!(),
457 }
458 }
459}