blob: 34f9b251c9c12f7fba7f1cc72a79cbce999944c2 [file]
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! Input filter thread implementation in rust.
//! Using IInputFilter.aidl interface to create ever looping thread with JNI support, rest of
//! thread handling is done from rust side.
//!
//! NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't
//! have JNI support and can't call into Java policy that we use currently. libutils provided
//! Thread.h also recommends against using std::thread and using the provided infrastructure that
//! already provides way of attaching JniEnv to the created thread. So, we are using an AIDL
//! interface to expose the InputThread infrastructure to rust.
use crate::input_filter::InputFilterThreadCreator;
use binder::{BinderFeatures, Interface, Strong};
use com_android_server_inputflinger::aidl::com::android::server::inputflinger::IInputThread::{
IInputThread, IInputThreadCallback::BnInputThreadCallback,
IInputThreadCallback::IInputThreadCallback,
};
use log::{debug, error};
use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
use std::sync::{Arc, RwLock, RwLockWriteGuard};
/// Interface to receive callback from Input filter thread
pub trait ThreadCallback {
/// Calls back after the requested timeout expires.
/// {@see InputFilterThread.request_timeout_at_time(...)}
///
/// NOTE: In case of multiple requests, the timeout request which is earliest in time, will be
/// fulfilled and notified to all the listeners. It's up to the listeners to re-request another
/// timeout in the future.
fn notify_timeout_expired(&self, when_nanos: i64);
/// Unique name for the listener, which will be used to uniquely identify the listener.
fn name(&self) -> &str;
}
#[derive(Clone)]
pub struct InputFilterThread {
thread_creator: InputFilterThreadCreator,
thread_callback_handler: ThreadCallbackHandler,
inner: Arc<RwLock<InputFilterThreadInner>>,
looper: Arc<RwLock<Looper>>,
}
struct InputFilterThreadInner {
next_timeout: i64,
is_finishing: bool,
}
struct Looper {
cpp_thread: Option<Strong<dyn IInputThread>>,
}
impl InputFilterThread {
/// Create a new InputFilterThread instance.
/// NOTE: This will create a new thread. Clone the existing instance to reuse the same thread.
pub fn new(thread_creator: InputFilterThreadCreator) -> InputFilterThread {
Self {
thread_creator,
thread_callback_handler: ThreadCallbackHandler::new(),
inner: Arc::new(RwLock::new(InputFilterThreadInner {
next_timeout: i64::MAX,
is_finishing: false,
})),
looper: Arc::new(RwLock::new(Looper { cpp_thread: None })),
}
}
/// Listener requesting a timeout in future will receive a callback at or before the requested
/// time on the input filter thread.
/// {@see ThreadCallback.notify_timeout_expired(...)}
pub fn request_timeout_at_time(&self, when_nanos: i64) {
let mut need_wake = false;
{
// acquire filter lock
let filter_thread = &mut self.filter_thread();
if when_nanos < filter_thread.next_timeout {
filter_thread.next_timeout = when_nanos;
need_wake = true;
}
} // release filter lock
if need_wake {
self.wake();
}
}
/// Registers a callback listener.
///
/// NOTE: If a listener with the same name already exists when registering using
/// {@see InputFilterThread.register_thread_callback(...)}, we will ignore the listener. You
/// must clear any previously registered listeners using
/// {@see InputFilterThread.unregister_thread_callback(...) before registering the new listener.
///
/// NOTE: Also, registering a callback will start the looper if not already started.
pub fn register_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
self.thread_callback_handler.register_thread_callback(callback);
self.start();
}
/// Unregisters a callback listener.
///
/// NOTE: Unregistering a callback will stop the looper if not other callback registered.
pub fn unregister_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
self.thread_callback_handler.unregister_thread_callback(callback);
// Stop the thread if no registered callbacks exist. We will recreate the thread when new
// callbacks are registered.
let has_callbacks = self.thread_callback_handler.has_callbacks();
if !has_callbacks {
self.stop();
}
}
fn start(&self) {
debug!("InputFilterThread: start thread");
{
// acquire looper lock
let looper = &mut self.looper();
if looper.cpp_thread.is_none() {
looper.cpp_thread = Some(self.thread_creator.create(
&BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()),
));
}
} // release looper lock
self.set_finishing(false);
}
fn stop(&self) {
debug!("InputFilterThread: stop thread");
self.set_finishing(true);
self.wake();
{
// acquire looper lock
let looper = &mut self.looper();
if let Some(cpp_thread) = &looper.cpp_thread {
let _ = cpp_thread.finish();
}
// Clear all references
looper.cpp_thread = None;
} // release looper lock
}
fn set_finishing(&self, is_finishing: bool) {
let filter_thread = &mut self.filter_thread();
filter_thread.is_finishing = is_finishing;
}
fn loop_once(&self, now: i64) {
let mut wake_up_time = i64::MAX;
let mut timeout_expired = false;
{
// acquire thread lock
let filter_thread = &mut self.filter_thread();
if filter_thread.is_finishing {
// Thread is finishing so don't block processing on it and let it loop.
return;
}
if filter_thread.next_timeout != i64::MAX {
if filter_thread.next_timeout <= now {
timeout_expired = true;
filter_thread.next_timeout = i64::MAX;
} else {
wake_up_time = filter_thread.next_timeout;
}
}
} // release thread lock
if timeout_expired {
self.thread_callback_handler.notify_timeout_expired(now);
}
self.sleep_until(wake_up_time);
}
fn filter_thread(&self) -> RwLockWriteGuard<'_, InputFilterThreadInner> {
self.inner.write().unwrap()
}
fn sleep_until(&self, when_nanos: i64) {
let looper = self.looper.read().unwrap();
if let Some(cpp_thread) = &looper.cpp_thread {
let _ = cpp_thread.sleepUntil(when_nanos);
}
}
fn wake(&self) {
let looper = self.looper.read().unwrap();
if let Some(cpp_thread) = &looper.cpp_thread {
let _ = cpp_thread.wake();
}
}
fn looper(&self) -> RwLockWriteGuard<'_, Looper> {
self.looper.write().unwrap()
}
}
impl Interface for InputFilterThread {}
impl IInputThreadCallback for InputFilterThread {
fn loopOnce(&self) -> binder::Result<()> {
self.loop_once(clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds());
Result::Ok(())
}
}
#[derive(Default, Clone)]
struct ThreadCallbackHandler(Arc<RwLock<ThreadCallbackHandlerInner>>);
#[derive(Default)]
struct ThreadCallbackHandlerInner {
callbacks: Vec<Box<dyn ThreadCallback + Send + Sync>>,
}
impl ThreadCallbackHandler {
fn new() -> Self {
Default::default()
}
fn has_callbacks(&self) -> bool {
!&self.0.read().unwrap().callbacks.is_empty()
}
fn register_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
let callbacks = &mut self.0.write().unwrap().callbacks;
if callbacks.iter().any(|x| x.name() == callback.name()) {
error!(
"InputFilterThread: register_thread_callback, callback {:?} already exists!",
callback.name()
);
return;
}
debug!(
"InputFilterThread: register_thread_callback, callback {:?} added!",
callback.name()
);
callbacks.push(callback);
}
fn unregister_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
let callbacks = &mut self.0.write().unwrap().callbacks;
if let Some(index) = callbacks.iter().position(|x| x.name() == callback.name()) {
callbacks.remove(index);
debug!(
"InputFilterThread: unregister_thread_callback, callback {:?} removed!",
callback.name()
);
return;
}
error!(
"InputFilterThread: unregister_thread_callback, callback {:?} doesn't exist",
callback.name()
);
}
fn notify_timeout_expired(&self, when_nanos: i64) {
let callbacks = &self.0.read().unwrap().callbacks;
for callback in callbacks.iter() {
callback.notify_timeout_expired(when_nanos);
}
}
}
#[cfg(test)]
mod tests {
use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator};
use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread};
use binder::Strong;
use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
use std::sync::{Arc, RwLock};
use std::time::Duration;
#[test]
fn test_register_callback_creates_cpp_thread() {
let test_callbacks = TestCallbacks::new();
let test_thread = get_thread(test_callbacks.clone());
let test_thread_callback = TestThreadCallback::new();
test_thread.register_thread_callback(Box::new(test_thread_callback));
assert!(test_callbacks.is_thread_running());
}
#[test]
fn test_unregister_callback_finishes_cpp_thread() {
let test_callbacks = TestCallbacks::new();
let test_thread = get_thread(test_callbacks.clone());
let test_thread_callback = TestThreadCallback::new();
test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
test_thread.unregister_thread_callback(Box::new(test_thread_callback));
assert!(!test_callbacks.is_thread_running());
}
#[test]
fn test_notify_timeout_called_after_timeout_expired() {
let test_callbacks = TestCallbacks::new();
let test_thread = get_thread(test_callbacks.clone());
let test_thread_callback = TestThreadCallback::new();
test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds();
test_thread.request_timeout_at_time((now + 10) * 1000000);
std::thread::sleep(Duration::from_millis(100));
assert!(test_thread_callback.is_notify_timeout_called());
}
#[test]
fn test_notify_timeout_not_called_before_timeout_expired() {
let test_callbacks = TestCallbacks::new();
let test_thread = get_thread(test_callbacks.clone());
let test_thread_callback = TestThreadCallback::new();
test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds();
test_thread.request_timeout_at_time((now + 100) * 1000000);
std::thread::sleep(Duration::from_millis(10));
assert!(!test_thread_callback.is_notify_timeout_called());
}
fn get_thread(callbacks: TestCallbacks) -> InputFilterThread {
InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new(
Box::new(callbacks),
)))))
}
}
#[cfg(test)]
pub mod test_thread_callback {
use crate::input_filter_thread::ThreadCallback;
use std::sync::{Arc, RwLock, RwLockWriteGuard};
#[derive(Default)]
struct TestThreadCallbackInner {
is_notify_timeout_called: bool,
}
#[derive(Default, Clone)]
pub struct TestThreadCallback(Arc<RwLock<TestThreadCallbackInner>>);
impl TestThreadCallback {
pub fn new() -> Self {
Default::default()
}
fn inner(&self) -> RwLockWriteGuard<'_, TestThreadCallbackInner> {
self.0.write().unwrap()
}
pub fn is_notify_timeout_called(&self) -> bool {
self.0.read().unwrap().is_notify_timeout_called
}
}
impl ThreadCallback for TestThreadCallback {
fn notify_timeout_expired(&self, _when_nanos: i64) {
self.inner().is_notify_timeout_called = true;
}
fn name(&self) -> &str {
"TestThreadCallback"
}
}
}