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}