summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmds/dumpstate/Android.bp3
-rw-r--r--libs/binder/Android.bp4
-rw-r--r--libs/binder/rust/src/lib.rs15
-rw-r--r--libs/binder/rust/src/native.rs114
-rw-r--r--libs/binder/rust/src/proxy.rs98
-rw-r--r--libs/binder/rust/src/service.rs230
-rw-r--r--libs/binder/trusty/build-config-usertests1
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk34
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/main.rs36
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/manifest.json9
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/rules.mk35
-rw-r--r--libs/binder/trusty/usertests-inc.mk3
-rw-r--r--libs/bufferqueueconverter/Android.bp3
-rw-r--r--libs/gralloc/types/Android.bp5
-rw-r--r--libs/gui/Android.bp4
-rw-r--r--libs/gui/rust/aidl_types/src/lib.rs1
-rw-r--r--libs/ui/Android.bp3
-rw-r--r--services/inputflinger/InputCommonConverter.cpp30
-rw-r--r--services/inputflinger/InputCommonConverter.h17
-rw-r--r--services/inputflinger/InputFilterCallbacks.cpp44
-rw-r--r--services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl17
-rw-r--r--services/inputflinger/dispatcher/InputState.cpp5
-rw-r--r--services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp14
-rw-r--r--services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h3
-rw-r--r--services/inputflinger/dispatcher/trace/ThreadedBackend.cpp35
-rw-r--r--services/inputflinger/dispatcher/trace/ThreadedBackend.h12
-rw-r--r--services/inputflinger/include/NotifyArgsBuilders.h29
-rw-r--r--services/inputflinger/reader/mapper/TouchInputMapper.cpp43
-rw-r--r--services/inputflinger/reader/mapper/TouchInputMapper.h22
-rw-r--r--services/inputflinger/reader/mapper/TouchpadInputMapper.cpp2
-rw-r--r--services/inputflinger/rust/input_filter.rs106
-rw-r--r--services/inputflinger/rust/input_filter_thread.rs254
-rw-r--r--services/inputflinger/rust/slow_keys_filter.rs138
-rw-r--r--services/inputflinger/tests/Android.bp2
-rw-r--r--services/inputflinger/tests/FakeInputDispatcherPolicy.cpp11
-rw-r--r--services/inputflinger/tests/FakeInputDispatcherPolicy.h3
-rw-r--r--services/inputflinger/tests/FakeWindows.h20
-rw-r--r--services/inputflinger/tests/InputReader_test.cpp161
-rw-r--r--services/inputflinger/tests/InputTraceSession.cpp209
-rw-r--r--services/inputflinger/tests/InputTraceSession.h85
-rw-r--r--services/inputflinger/tests/InputTracingTest.cpp732
-rw-r--r--services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp5
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp5
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp21
-rw-r--r--services/surfaceflinger/OWNERS2
-rw-r--r--services/surfaceflinger/SurfaceFlinger.cpp39
-rw-r--r--services/vibratorservice/test/VibratorHalControllerTest.cpp13
-rw-r--r--services/vibratorservice/test/test_utils.h28
48 files changed, 1968 insertions, 737 deletions
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index 341fabb32a..de0aafaa1c 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -37,9 +37,6 @@ cc_library_shared {
name: "libdumpstateutil",
defaults: ["dumpstate_cflag_defaults"],
vendor_available: true,
- vndk: {
- enabled: true,
- },
srcs: [
"DumpstateInternal.cpp",
"DumpstateUtil.cpp",
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index eec12e4cd5..9c24dc590c 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -358,9 +358,6 @@ cc_library {
// for vndbinder
vendor_available: true,
- vndk: {
- enabled: true,
- },
recovery_available: true,
double_loadable: true,
// TODO(b/153609531): remove when no longer needed.
@@ -669,6 +666,7 @@ cc_library {
"//packages/modules/Virtualization:__subpackages__",
"//device/google/cuttlefish/shared/minidroid:__subpackages__",
"//system/software_defined_vehicle:__subpackages__",
+ "//visibility:any_system_partition",
],
}
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 16049f28c5..0540ed379b 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -101,6 +101,8 @@ mod native;
mod parcel;
mod proxy;
#[cfg(not(target_os = "trusty"))]
+mod service;
+#[cfg(not(target_os = "trusty"))]
mod state;
use binder_ndk_sys as sys;
@@ -108,14 +110,13 @@ use binder_ndk_sys as sys;
pub use crate::binder_async::{BinderAsyncPool, BoxFuture};
pub use binder::{BinderFeatures, FromIBinder, IBinder, Interface, Strong, Weak};
pub use error::{ExceptionCode, IntoBinderResult, Status, StatusCode};
-pub use native::{
- add_service, force_lazy_services_persist, is_handling_transaction, register_lazy_service,
- LazyServiceGuard,
-};
pub use parcel::{ParcelFileDescriptor, Parcelable, ParcelableHolder};
-pub use proxy::{
- get_declared_instances, get_interface, get_service, is_declared, wait_for_interface,
- wait_for_service, DeathRecipient, SpIBinder, WpIBinder,
+pub use proxy::{DeathRecipient, SpIBinder, WpIBinder};
+#[cfg(not(target_os = "trusty"))]
+pub use service::{
+ add_service, force_lazy_services_persist, get_declared_instances, get_interface, get_service,
+ is_declared, is_handling_transaction, register_lazy_service, wait_for_interface,
+ wait_for_service, LazyServiceGuard,
};
#[cfg(not(target_os = "trusty"))]
pub use state::{ProcessState, ThreadState};
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index 8ae010ea88..da9d7dc948 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -23,12 +23,11 @@ use crate::proxy::SpIBinder;
use crate::sys;
use std::convert::TryFrom;
-use std::ffi::{c_void, CStr, CString};
+use std::ffi::{c_void, CStr};
use std::io::Write;
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::os::raw::c_char;
-use std::sync::Mutex;
/// Rust wrapper around Binder remotable objects.
///
@@ -462,110 +461,6 @@ unsafe impl<B: Remotable> AsNative<sys::AIBinder> for Binder<B> {
}
}
-/// Register a new service with the default service manager.
-///
-/// Registers the given binder object with the given identifier. If successful,
-/// this service can then be retrieved using that identifier.
-///
-/// This function will panic if the identifier contains a 0 byte (NUL).
-pub fn add_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
- let instance = CString::new(identifier).unwrap();
- let status =
- // Safety: `AServiceManager_addService` expects valid `AIBinder` and C
- // string pointers. Caller retains ownership of both pointers.
- // `AServiceManager_addService` creates a new strong reference and copies
- // the string, so both pointers need only be valid until the call returns.
- unsafe { sys::AServiceManager_addService(binder.as_native_mut(), instance.as_ptr()) };
- status_result(status)
-}
-
-/// Register a dynamic service via the LazyServiceRegistrar.
-///
-/// Registers the given binder object with the given identifier. If successful,
-/// this service can then be retrieved using that identifier. The service process
-/// will be shut down once all registered services are no longer in use.
-///
-/// If any service in the process is registered as lazy, all should be, otherwise
-/// the process may be shut down while a service is in use.
-///
-/// This function will panic if the identifier contains a 0 byte (NUL).
-pub fn register_lazy_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
- let instance = CString::new(identifier).unwrap();
- // Safety: `AServiceManager_registerLazyService` expects valid `AIBinder` and C
- // string pointers. Caller retains ownership of both
- // pointers. `AServiceManager_registerLazyService` creates a new strong reference
- // and copies the string, so both pointers need only be valid until the
- // call returns.
- let status = unsafe {
- sys::AServiceManager_registerLazyService(binder.as_native_mut(), instance.as_ptr())
- };
- status_result(status)
-}
-
-/// Prevent a process which registers lazy services from being shut down even when none
-/// of the services is in use.
-///
-/// If persist is true then shut down will be blocked until this function is called again with
-/// persist false. If this is to be the initial state, call this function before calling
-/// register_lazy_service.
-///
-/// Consider using [`LazyServiceGuard`] rather than calling this directly.
-pub fn force_lazy_services_persist(persist: bool) {
- // Safety: No borrowing or transfer of ownership occurs here.
- unsafe { sys::AServiceManager_forceLazyServicesPersist(persist) }
-}
-
-/// An RAII object to ensure a process which registers lazy services is not killed. During the
-/// lifetime of any of these objects the service manager will not not kill the process even if none
-/// of its lazy services are in use.
-#[must_use]
-#[derive(Debug)]
-pub struct LazyServiceGuard {
- // Prevent construction outside this module.
- _private: (),
-}
-
-// Count of how many LazyServiceGuard objects are in existence.
-static GUARD_COUNT: Mutex<u64> = Mutex::new(0);
-
-impl LazyServiceGuard {
- /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this
- /// process.
- pub fn new() -> Self {
- let mut count = GUARD_COUNT.lock().unwrap();
- *count += 1;
- if *count == 1 {
- // It's important that we make this call with the mutex held, to make sure
- // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly
- // sequenced. (That also means we can't just use an AtomicU64.)
- force_lazy_services_persist(true);
- }
- Self { _private: () }
- }
-}
-
-impl Drop for LazyServiceGuard {
- fn drop(&mut self) {
- let mut count = GUARD_COUNT.lock().unwrap();
- *count -= 1;
- if *count == 0 {
- force_lazy_services_persist(false);
- }
- }
-}
-
-impl Clone for LazyServiceGuard {
- fn clone(&self) -> Self {
- Self::new()
- }
-}
-
-impl Default for LazyServiceGuard {
- fn default() -> Self {
- Self::new()
- }
-}
-
/// Tests often create a base BBinder instance; so allowing the unit
/// type to be remotable translates nicely to Binder::new(()).
impl Remotable for () {
@@ -590,10 +485,3 @@ impl Remotable for () {
}
impl Interface for () {}
-
-/// Determine whether the current thread is currently executing an incoming
-/// transaction.
-pub fn is_handling_transaction() -> bool {
- // Safety: This method is always safe to call.
- unsafe { sys::AIBinder_isHandlingTransaction() }
-}
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index 7434e9ddbd..340014aeaa 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -29,11 +29,10 @@ use crate::sys;
use std::cmp::Ordering;
use std::convert::TryInto;
-use std::ffi::{c_void, CStr, CString};
+use std::ffi::{c_void, CString};
use std::fmt;
use std::mem;
use std::os::fd::AsRawFd;
-use std::os::raw::c_char;
use std::ptr;
use std::sync::Arc;
@@ -129,14 +128,6 @@ impl SpIBinder {
}
}
-fn interface_cast<T: FromIBinder + ?Sized>(service: Option<SpIBinder>) -> Result<Strong<T>> {
- if let Some(service) = service {
- FromIBinder::try_from(service)
- } else {
- Err(StatusCode::NAME_NOT_FOUND)
- }
-}
-
pub mod unstable_api {
use super::{sys, SpIBinder};
@@ -739,93 +730,6 @@ unsafe impl<T: Proxy> AsNative<sys::AIBinder> for T {
}
}
-/// Retrieve an existing service, blocking for a few seconds if it doesn't yet
-/// exist.
-pub fn get_service(name: &str) -> Option<SpIBinder> {
- let name = CString::new(name).ok()?;
- // Safety: `AServiceManager_getService` returns either a null pointer or a
- // valid pointer to an owned `AIBinder`. Either of these values is safe to
- // pass to `SpIBinder::from_raw`.
- unsafe { SpIBinder::from_raw(sys::AServiceManager_getService(name.as_ptr())) }
-}
-
-/// Retrieve an existing service, or start it if it is configured as a dynamic
-/// service and isn't yet started.
-pub fn wait_for_service(name: &str) -> Option<SpIBinder> {
- let name = CString::new(name).ok()?;
- // Safety: `AServiceManager_waitforService` returns either a null pointer or
- // a valid pointer to an owned `AIBinder`. Either of these values is safe to
- // pass to `SpIBinder::from_raw`.
- unsafe { SpIBinder::from_raw(sys::AServiceManager_waitForService(name.as_ptr())) }
-}
-
-/// Retrieve an existing service for a particular interface, blocking for a few
-/// seconds if it doesn't yet exist.
-pub fn get_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
- interface_cast(get_service(name))
-}
-
-/// Retrieve an existing service for a particular interface, or start it if it
-/// is configured as a dynamic service and isn't yet started.
-pub fn wait_for_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
- interface_cast(wait_for_service(name))
-}
-
-/// Check if a service is declared (e.g. in a VINTF manifest)
-pub fn is_declared(interface: &str) -> Result<bool> {
- let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
-
- // Safety: `interface` is a valid null-terminated C-style string and is only
- // borrowed for the lifetime of the call. The `interface` local outlives
- // this call as it lives for the function scope.
- unsafe { Ok(sys::AServiceManager_isDeclared(interface.as_ptr())) }
-}
-
-/// Retrieve all declared instances for a particular interface
-///
-/// For instance, if 'android.foo.IFoo/foo' is declared, and 'android.foo.IFoo'
-/// is passed here, then ["foo"] would be returned.
-pub fn get_declared_instances(interface: &str) -> Result<Vec<String>> {
- unsafe extern "C" fn callback(instance: *const c_char, opaque: *mut c_void) {
- // Safety: opaque was a mutable pointer created below from a Vec of
- // CString, and outlives this callback. The null handling here is just
- // to avoid the possibility of unwinding across C code if this crate is
- // ever compiled with panic=unwind.
- if let Some(instances) = unsafe { opaque.cast::<Vec<CString>>().as_mut() } {
- // Safety: instance is a valid null-terminated C string with a
- // lifetime at least as long as this function, and we immediately
- // copy it into an owned CString.
- unsafe {
- instances.push(CStr::from_ptr(instance).to_owned());
- }
- } else {
- eprintln!("Opaque pointer was null in get_declared_instances callback!");
- }
- }
-
- let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
- let mut instances: Vec<CString> = vec![];
- // Safety: `interface` and `instances` are borrowed for the length of this
- // call and both outlive the call. `interface` is guaranteed to be a valid
- // null-terminated C-style string.
- unsafe {
- sys::AServiceManager_forEachDeclaredInstance(
- interface.as_ptr(),
- &mut instances as *mut _ as *mut c_void,
- Some(callback),
- );
- }
-
- instances
- .into_iter()
- .map(CString::into_string)
- .collect::<std::result::Result<Vec<String>, _>>()
- .map_err(|e| {
- eprintln!("An interface instance name was not a valid UTF-8 string: {}", e);
- StatusCode::BAD_VALUE
- })
-}
-
/// Safety: `SpIBinder` guarantees that `binder` always contains a valid pointer
/// to an `AIBinder`, so we can trivially extract this pointer here.
unsafe impl AsNative<sys::AIBinder> for SpIBinder {
diff --git a/libs/binder/rust/src/service.rs b/libs/binder/rust/src/service.rs
new file mode 100644
index 0000000000..3ca3b540c4
--- /dev/null
+++ b/libs/binder/rust/src/service.rs
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 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.
+ */
+
+use crate::binder::{AsNative, FromIBinder, Strong};
+use crate::error::{status_result, Result, StatusCode};
+use crate::proxy::SpIBinder;
+use crate::sys;
+
+use std::ffi::{c_void, CStr, CString};
+use std::os::raw::c_char;
+use std::sync::Mutex;
+
+/// Register a new service with the default service manager.
+///
+/// Registers the given binder object with the given identifier. If successful,
+/// this service can then be retrieved using that identifier.
+///
+/// This function will panic if the identifier contains a 0 byte (NUL).
+pub fn add_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
+ let instance = CString::new(identifier).unwrap();
+ let status =
+ // Safety: `AServiceManager_addService` expects valid `AIBinder` and C
+ // string pointers. Caller retains ownership of both pointers.
+ // `AServiceManager_addService` creates a new strong reference and copies
+ // the string, so both pointers need only be valid until the call returns.
+ unsafe { sys::AServiceManager_addService(binder.as_native_mut(), instance.as_ptr()) };
+ status_result(status)
+}
+
+/// Register a dynamic service via the LazyServiceRegistrar.
+///
+/// Registers the given binder object with the given identifier. If successful,
+/// this service can then be retrieved using that identifier. The service process
+/// will be shut down once all registered services are no longer in use.
+///
+/// If any service in the process is registered as lazy, all should be, otherwise
+/// the process may be shut down while a service is in use.
+///
+/// This function will panic if the identifier contains a 0 byte (NUL).
+pub fn register_lazy_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
+ let instance = CString::new(identifier).unwrap();
+ // Safety: `AServiceManager_registerLazyService` expects valid `AIBinder` and C
+ // string pointers. Caller retains ownership of both
+ // pointers. `AServiceManager_registerLazyService` creates a new strong reference
+ // and copies the string, so both pointers need only be valid until the
+ // call returns.
+ let status = unsafe {
+ sys::AServiceManager_registerLazyService(binder.as_native_mut(), instance.as_ptr())
+ };
+ status_result(status)
+}
+
+/// Prevent a process which registers lazy services from being shut down even when none
+/// of the services is in use.
+///
+/// If persist is true then shut down will be blocked until this function is called again with
+/// persist false. If this is to be the initial state, call this function before calling
+/// register_lazy_service.
+///
+/// Consider using [`LazyServiceGuard`] rather than calling this directly.
+pub fn force_lazy_services_persist(persist: bool) {
+ // Safety: No borrowing or transfer of ownership occurs here.
+ unsafe { sys::AServiceManager_forceLazyServicesPersist(persist) }
+}
+
+/// An RAII object to ensure a process which registers lazy services is not killed. During the
+/// lifetime of any of these objects the service manager will not kill the process even if none
+/// of its lazy services are in use.
+#[must_use]
+#[derive(Debug)]
+pub struct LazyServiceGuard {
+ // Prevent construction outside this module.
+ _private: (),
+}
+
+// Count of how many LazyServiceGuard objects are in existence.
+static GUARD_COUNT: Mutex<u64> = Mutex::new(0);
+
+impl LazyServiceGuard {
+ /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this
+ /// process.
+ pub fn new() -> Self {
+ let mut count = GUARD_COUNT.lock().unwrap();
+ *count += 1;
+ if *count == 1 {
+ // It's important that we make this call with the mutex held, to make sure
+ // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly
+ // sequenced. (That also means we can't just use an AtomicU64.)
+ force_lazy_services_persist(true);
+ }
+ Self { _private: () }
+ }
+}
+
+impl Drop for LazyServiceGuard {
+ fn drop(&mut self) {
+ let mut count = GUARD_COUNT.lock().unwrap();
+ *count -= 1;
+ if *count == 0 {
+ force_lazy_services_persist(false);
+ }
+ }
+}
+
+impl Clone for LazyServiceGuard {
+ fn clone(&self) -> Self {
+ Self::new()
+ }
+}
+
+impl Default for LazyServiceGuard {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// Determine whether the current thread is currently executing an incoming
+/// transaction.
+pub fn is_handling_transaction() -> bool {
+ // Safety: This method is always safe to call.
+ unsafe { sys::AIBinder_isHandlingTransaction() }
+}
+
+fn interface_cast<T: FromIBinder + ?Sized>(service: Option<SpIBinder>) -> Result<Strong<T>> {
+ if let Some(service) = service {
+ FromIBinder::try_from(service)
+ } else {
+ Err(StatusCode::NAME_NOT_FOUND)
+ }
+}
+
+/// Retrieve an existing service, blocking for a few seconds if it doesn't yet
+/// exist.
+pub fn get_service(name: &str) -> Option<SpIBinder> {
+ let name = CString::new(name).ok()?;
+ // Safety: `AServiceManager_getService` returns either a null pointer or a
+ // valid pointer to an owned `AIBinder`. Either of these values is safe to
+ // pass to `SpIBinder::from_raw`.
+ unsafe { SpIBinder::from_raw(sys::AServiceManager_getService(name.as_ptr())) }
+}
+
+/// Retrieve an existing service, or start it if it is configured as a dynamic
+/// service and isn't yet started.
+pub fn wait_for_service(name: &str) -> Option<SpIBinder> {
+ let name = CString::new(name).ok()?;
+ // Safety: `AServiceManager_waitforService` returns either a null pointer or
+ // a valid pointer to an owned `AIBinder`. Either of these values is safe to
+ // pass to `SpIBinder::from_raw`.
+ unsafe { SpIBinder::from_raw(sys::AServiceManager_waitForService(name.as_ptr())) }
+}
+
+/// Retrieve an existing service for a particular interface, blocking for a few
+/// seconds if it doesn't yet exist.
+pub fn get_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
+ interface_cast(get_service(name))
+}
+
+/// Retrieve an existing service for a particular interface, or start it if it
+/// is configured as a dynamic service and isn't yet started.
+pub fn wait_for_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
+ interface_cast(wait_for_service(name))
+}
+
+/// Check if a service is declared (e.g. in a VINTF manifest)
+pub fn is_declared(interface: &str) -> Result<bool> {
+ let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
+
+ // Safety: `interface` is a valid null-terminated C-style string and is only
+ // borrowed for the lifetime of the call. The `interface` local outlives
+ // this call as it lives for the function scope.
+ unsafe { Ok(sys::AServiceManager_isDeclared(interface.as_ptr())) }
+}
+
+/// Retrieve all declared instances for a particular interface
+///
+/// For instance, if 'android.foo.IFoo/foo' is declared, and 'android.foo.IFoo'
+/// is passed here, then ["foo"] would be returned.
+pub fn get_declared_instances(interface: &str) -> Result<Vec<String>> {
+ unsafe extern "C" fn callback(instance: *const c_char, opaque: *mut c_void) {
+ // Safety: opaque was a mutable pointer created below from a Vec of
+ // CString, and outlives this callback. The null handling here is just
+ // to avoid the possibility of unwinding across C code if this crate is
+ // ever compiled with panic=unwind.
+ if let Some(instances) = unsafe { opaque.cast::<Vec<CString>>().as_mut() } {
+ // Safety: instance is a valid null-terminated C string with a
+ // lifetime at least as long as this function, and we immediately
+ // copy it into an owned CString.
+ unsafe {
+ instances.push(CStr::from_ptr(instance).to_owned());
+ }
+ } else {
+ eprintln!("Opaque pointer was null in get_declared_instances callback!");
+ }
+ }
+
+ let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
+ let mut instances: Vec<CString> = vec![];
+ // Safety: `interface` and `instances` are borrowed for the length of this
+ // call and both outlive the call. `interface` is guaranteed to be a valid
+ // null-terminated C-style string.
+ unsafe {
+ sys::AServiceManager_forEachDeclaredInstance(
+ interface.as_ptr(),
+ &mut instances as *mut _ as *mut c_void,
+ Some(callback),
+ );
+ }
+
+ instances
+ .into_iter()
+ .map(CString::into_string)
+ .collect::<std::result::Result<Vec<String>, _>>()
+ .map_err(|e| {
+ eprintln!("An interface instance name was not a valid UTF-8 string: {}", e);
+ StatusCode::BAD_VALUE
+ })
+}
diff --git a/libs/binder/trusty/build-config-usertests b/libs/binder/trusty/build-config-usertests
index d0a1fbca49..72e5ff9b2a 100644
--- a/libs/binder/trusty/build-config-usertests
+++ b/libs/binder/trusty/build-config-usertests
@@ -16,4 +16,5 @@
[
porttest("com.android.trusty.binderRpcTest"),
+ porttest("com.android.trusty.rust.binder_rpc_test.test"),
]
diff --git a/libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk
new file mode 100644
index 0000000000..1b0dca09f4
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2023 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_TESTS_DIR := $(LOCAL_DIR)/../../../../tests
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_AIDL_LANGUAGE := rust
+
+MODULE_CRATE_NAME := binder_rpc_test_aidl
+
+MODULE_AIDLS := \
+ $(LIBBINDER_TESTS_DIR)/BinderRpcTestClientInfo.aidl \
+ $(LIBBINDER_TESTS_DIR)/BinderRpcTestServerConfig.aidl \
+ $(LIBBINDER_TESTS_DIR)/BinderRpcTestServerInfo.aidl \
+ $(LIBBINDER_TESTS_DIR)/IBinderRpcCallback.aidl \
+ $(LIBBINDER_TESTS_DIR)/IBinderRpcSession.aidl \
+ $(LIBBINDER_TESTS_DIR)/IBinderRpcTest.aidl \
+ $(LIBBINDER_TESTS_DIR)/ParcelableCertificateData.aidl \
+
+include make/aidl.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_test/main.rs b/libs/binder/trusty/rust/binder_rpc_test/main.rs
new file mode 100644
index 0000000000..a71ce2c26e
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/main.rs
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+#![cfg(test)]
+
+use binder::{IBinder, Strong};
+use binder_rpc_test_aidl::aidl::IBinderRpcTest::IBinderRpcTest;
+use rpcbinder::RpcSession;
+use trusty_std::ffi::{CString, FallibleCString};
+
+test::init!();
+
+const SERVICE_PORT: &str = "com.android.trusty.binderRpcTestService.V1";
+
+fn get_service() -> Strong<dyn IBinderRpcTest> {
+ let port = CString::try_new(SERVICE_PORT).expect("Failed to allocate port name");
+ RpcSession::new().setup_trusty_client(port.as_c_str()).expect("Failed to create session")
+}
+
+#[test]
+fn ping() {
+ let srv = get_service();
+ assert_eq!(srv.as_binder().ping_binder(), Ok(()));
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/manifest.json b/libs/binder/trusty/rust/binder_rpc_test/manifest.json
new file mode 100644
index 0000000000..c2ecaa4fe3
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/manifest.json
@@ -0,0 +1,9 @@
+{
+ "uuid": "91eed949-8a9e-4569-9c83-5935fb624025",
+ "app_name": "rust_binder_rpc_test",
+ "min_heap": 16384,
+ "min_stack": 16384,
+ "mgmt_flags": {
+ "non_critical_app": true
+ }
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/rules.mk
new file mode 100644
index 0000000000..192a1591ae
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/rules.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2023 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/main.rs
+
+MODULE_CRATE_NAME := binder_rpc_test
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty/rust \
+ $(LIBBINDER_DIR)/trusty/rust/rpcbinder \
+ $(LOCAL_DIR)/aidl \
+ trusty/user/base/lib/trusty-std \
+
+MODULE_RUST_TESTS := true
+
+MANIFEST := $(LOCAL_DIR)/manifest.json
+
+include make/library.mk
diff --git a/libs/binder/trusty/usertests-inc.mk b/libs/binder/trusty/usertests-inc.mk
index 13001219d4..241e6683e7 100644
--- a/libs/binder/trusty/usertests-inc.mk
+++ b/libs/binder/trusty/usertests-inc.mk
@@ -17,3 +17,6 @@ TRUSTY_USER_TESTS += \
frameworks/native/libs/binder/trusty/binderRpcTest \
frameworks/native/libs/binder/trusty/binderRpcTest/service \
+TRUSTY_RUST_USER_TESTS += \
+ frameworks/native/libs/binder/trusty/rust/binder_rpc_test \
+
diff --git a/libs/bufferqueueconverter/Android.bp b/libs/bufferqueueconverter/Android.bp
index 3c8c41fdc8..196161b3e2 100644
--- a/libs/bufferqueueconverter/Android.bp
+++ b/libs/bufferqueueconverter/Android.bp
@@ -17,9 +17,6 @@ cc_library_headers {
cc_library {
name: "libbufferqueueconverter",
vendor_available: true,
- vndk: {
- enabled: true,
- },
double_loadable: true,
srcs: [
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index f300da57d8..8dabc2c79e 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -38,10 +38,7 @@ cc_library {
},
vendor_available: true,
- vndk: {
- enabled: true,
- support_system_process: true,
- },
+ double_loadable: true,
apex_available: [
"//apex_available:platform",
"com.android.media.swcodec",
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index d1b2c5f025..6c45746335 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -305,10 +305,6 @@ cc_defaults {
cc_library_shared {
name: "libgui",
vendor_available: true,
- vndk: {
- enabled: true,
- private: true,
- },
double_loadable: true,
defaults: [
diff --git a/libs/gui/rust/aidl_types/src/lib.rs b/libs/gui/rust/aidl_types/src/lib.rs
index 3d29529d23..941b1f9db8 100644
--- a/libs/gui/rust/aidl_types/src/lib.rs
+++ b/libs/gui/rust/aidl_types/src/lib.rs
@@ -20,6 +20,7 @@ use binder::{
StatusCode,
};
+#[allow(dead_code)]
macro_rules! stub_unstructured_parcelable {
($name:ident) => {
/// Unimplemented stub parcelable.
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 9cb298a132..312a1e68b3 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -105,9 +105,6 @@ cc_library_static {
cc_library_shared {
name: "libui",
vendor_available: true,
- vndk: {
- enabled: true,
- },
double_loadable: true,
cflags: [
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 6ccd9e7697..417c1f333b 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -20,6 +20,9 @@ using namespace ::aidl::android::hardware::input;
namespace android {
+const static ui::Transform kIdentityTransform;
+const static std::array<uint8_t, 32> kInvalidHmac{};
+
static common::Source getSource(uint32_t source) {
static_assert(static_cast<common::Source>(AINPUT_SOURCE_UNKNOWN) == common::Source::UNKNOWN,
"SOURCE_UNKNOWN mismatch");
@@ -337,4 +340,31 @@ common::MotionEvent notifyMotionArgsToHalMotionEvent(const NotifyMotionArgs& arg
return event;
}
+MotionEvent toMotionEvent(const NotifyMotionArgs& args, const ui::Transform* transform,
+ const ui::Transform* rawTransform, const std::array<uint8_t, 32>* hmac) {
+ if (transform == nullptr) transform = &kIdentityTransform;
+ if (rawTransform == nullptr) rawTransform = &kIdentityTransform;
+ if (hmac == nullptr) hmac = &kInvalidHmac;
+
+ MotionEvent event;
+ event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action,
+ args.actionButton, args.flags, args.edgeFlags, args.metaState,
+ args.buttonState, args.classification, *transform, args.xPrecision,
+ args.yPrecision, args.xCursorPosition, args.yCursorPosition, *rawTransform,
+ args.downTime, args.eventTime, args.getPointerCount(),
+ args.pointerProperties.data(), args.pointerCoords.data());
+ return event;
+}
+
+KeyEvent toKeyEvent(const NotifyKeyArgs& args, int32_t repeatCount,
+ const std::array<uint8_t, 32>* hmac) {
+ if (hmac == nullptr) hmac = &kInvalidHmac;
+
+ KeyEvent event;
+ event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action,
+ args.flags, args.keyCode, args.scanCode, args.metaState, repeatCount,
+ args.downTime, args.eventTime);
+ return event;
+}
+
} // namespace android
diff --git a/services/inputflinger/InputCommonConverter.h b/services/inputflinger/InputCommonConverter.h
index 4d3b76885f..0d4cbb0c96 100644
--- a/services/inputflinger/InputCommonConverter.h
+++ b/services/inputflinger/InputCommonConverter.h
@@ -16,16 +16,25 @@
#pragma once
+#include "InputListener.h"
+
#include <aidl/android/hardware/input/common/Axis.h>
#include <aidl/android/hardware/input/common/MotionEvent.h>
-#include "InputListener.h"
+#include <input/Input.h>
namespace android {
-/**
- * Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent
- */
+/** Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent. */
::aidl::android::hardware::input::common::MotionEvent notifyMotionArgsToHalMotionEvent(
const NotifyMotionArgs& args);
+/** Convert from NotifyMotionArgs to MotionEvent. */
+MotionEvent toMotionEvent(const NotifyMotionArgs&, const ui::Transform* transform = nullptr,
+ const ui::Transform* rawTransform = nullptr,
+ const std::array<uint8_t, 32>* hmac = nullptr);
+
+/** Convert from NotifyKeyArgs to KeyEvent. */
+KeyEvent toKeyEvent(const NotifyKeyArgs&, int32_t repeatCount = 0,
+ const std::array<uint8_t, 32>* hmac = nullptr);
+
} // namespace android
diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp
index 6c3144230f..a9bdbec105 100644
--- a/services/inputflinger/InputFilterCallbacks.cpp
+++ b/services/inputflinger/InputFilterCallbacks.cpp
@@ -19,9 +19,10 @@
#include "InputFilterCallbacks.h"
#include <aidl/com/android/server/inputflinger/BnInputThread.h>
#include <android/binder_auto_utils.h>
+#include <utils/Looper.h>
#include <utils/StrongPointer.h>
-#include <utils/Thread.h>
#include <functional>
+#include "InputThread.h"
namespace android {
@@ -38,36 +39,37 @@ namespace {
using namespace aidl::com::android::server::inputflinger;
-class InputFilterThreadImpl : public Thread {
-public:
- explicit InputFilterThreadImpl(std::function<void()> loop)
- : Thread(/*canCallJava=*/true), mThreadLoop(loop) {}
-
- ~InputFilterThreadImpl() {}
-
-private:
- std::function<void()> mThreadLoop;
-
- bool threadLoop() override {
- mThreadLoop();
- return true;
- }
-};
-
class InputFilterThread : public BnInputThread {
public:
InputFilterThread(std::shared_ptr<IInputThreadCallback> callback) : mCallback(callback) {
- mThread = sp<InputFilterThreadImpl>::make([this]() { loopOnce(); });
- mThread->run("InputFilterThread", ANDROID_PRIORITY_URGENT_DISPLAY);
+ mLooper = sp<Looper>::make(/*allowNonCallbacks=*/false);
+ mThread = std::make_unique<InputThread>(
+ "InputFilter", [this]() { loopOnce(); }, [this]() { mLooper->wake(); });
}
ndk::ScopedAStatus finish() override {
- mThread->requestExit();
+ if (mThread && mThread->isCallingThread()) {
+ ALOGE("InputFilterThread cannot be stopped on itself!");
+ return ndk::ScopedAStatus::fromStatus(INVALID_OPERATION);
+ }
+ mThread.reset();
+ return ndk::ScopedAStatus::ok();
+ }
+
+ ndk::ScopedAStatus sleepUntil(nsecs_t when) override {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ mLooper->pollOnce(toMillisecondTimeoutDelay(now, when));
+ return ndk::ScopedAStatus::ok();
+ }
+
+ ndk::ScopedAStatus wake() override {
+ mLooper->wake();
return ndk::ScopedAStatus::ok();
}
private:
- sp<Thread> mThread;
+ sp<Looper> mLooper;
+ std::unique_ptr<InputThread> mThread;
std::shared_ptr<IInputThreadCallback> mCallback;
void loopOnce() { LOG_ALWAYS_FATAL_IF(!mCallback->loopOnce().isOk()); }
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
index 2f6b8fc6ff..cc0592ef52 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
@@ -21,6 +21,13 @@ package com.android.server.inputflinger;
* infrastructure.
*
* <p>
+ * Earlier, we used rust thread park()/unpark() to put the thread to sleep and wake up from sleep.
+ * But that caused some breakages after migrating the rust system crates to 2021 edition. Since,
+ * the threads are created in C++, it was more reliable to rely on C++ side of the implementation
+ * to implement the sleep and wake functions.
+ * </p>
+ *
+ * <p>
* 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
@@ -33,6 +40,16 @@ interface IInputThread {
/** Finish input thread (if not running, this call does nothing) */
void finish();
+ /** Wakes up the thread (if sleeping) */
+ void wake();
+
+ /**
+ * Puts the thread to sleep until a future time provided.
+ *
+ * NOTE: The thread can be awaken before the provided time using {@link wake()} function.
+ */
+ void sleepUntil(long whenNanos);
+
/** Callbacks from C++ to call into inputflinger rust components */
interface IInputThreadCallback {
/**
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index c2d2b7b0ea..02bc3680bf 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -338,9 +338,8 @@ bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry) cons
// it's unlikely that those two streams would be consistent with each other. Therefore,
// cancel the previous gesture if the display id changes.
if (motionEntry.displayId != lastMemento.displayId) {
- LOG(INFO) << "Canceling stream: last displayId was "
- << inputEventSourceToString(lastMemento.displayId) << " and new event is "
- << motionEntry;
+ LOG(INFO) << "Canceling stream: last displayId was " << lastMemento.displayId
+ << " and new event is " << motionEntry;
return true;
}
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
index 9c39743569..91ebe9bb5f 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -149,6 +149,8 @@ bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule,
// --- PerfettoBackend ---
+bool PerfettoBackend::sUseInProcessBackendForTest{false};
+
std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{};
std::atomic<int32_t> PerfettoBackend::sNextInstanceId{1};
@@ -159,7 +161,8 @@ PerfettoBackend::PerfettoBackend(GetPackageUid getPackagesForUid)
// we never unregister the InputEventDataSource.
std::call_once(sDataSourceRegistrationFlag, []() {
perfetto::TracingInitArgs args;
- args.backends = perfetto::kSystemBackend;
+ args.backends = sUseInProcessBackendForTest ? perfetto::kInProcessBackend
+ : perfetto::kSystemBackend;
perfetto::Tracing::Initialize(args);
// Register our custom data source for input event tracing.
@@ -175,6 +178,9 @@ void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event,
const TracedEventMetadata& metadata) {
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
auto dataSource = ctx.GetDataSourceLocked();
+ if (!dataSource.valid()) {
+ return;
+ }
dataSource->initializeUidMap(mGetPackageUid);
if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
return;
@@ -196,6 +202,9 @@ void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event,
const TracedEventMetadata& metadata) {
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
auto dataSource = ctx.GetDataSourceLocked();
+ if (!dataSource.valid()) {
+ return;
+ }
dataSource->initializeUidMap(mGetPackageUid);
if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
return;
@@ -217,6 +226,9 @@ void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs
const TracedEventMetadata& metadata) {
InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
auto dataSource = ctx.GetDataSourceLocked();
+ if (!dataSource.valid()) {
+ return;
+ }
dataSource->initializeUidMap(mGetPackageUid);
if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) {
return;
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
index e945066dff..fdfe495c45 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -51,6 +51,8 @@ class PerfettoBackend : public InputTracingBackendInterface {
public:
using GetPackageUid = std::function<gui::Uid(std::string)>;
+ static bool sUseInProcessBackendForTest;
+
explicit PerfettoBackend(GetPackageUid);
~PerfettoBackend() override = default;
@@ -61,6 +63,7 @@ public:
private:
// Implementation of the perfetto data source.
// Each instance of the InputEventDataSource represents a different tracing session.
+ // Its lifecycle is controlled by perfetto.
class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> {
public:
explicit InputEventDataSource();
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
index 77d09cbb4b..c0a98f5e5d 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -84,13 +84,18 @@ void ThreadedBackend<Backend>::threadLoop() {
std::unique_lock lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
+ setIdleStatus(true);
+
// Wait until we need to process more events or exit.
mThreadWakeCondition.wait(lock,
[&]() REQUIRES(mLock) { return mThreadExit || !mQueue.empty(); });
if (mThreadExit) {
+ setIdleStatus(true);
return;
}
+ setIdleStatus(false);
+
mQueue.swap(entries);
} // release lock
@@ -109,6 +114,36 @@ void ThreadedBackend<Backend>::threadLoop() {
entries.clear();
}
+template <typename Backend>
+std::function<void()> ThreadedBackend<Backend>::getIdleWaiterForTesting() {
+ std::scoped_lock lock(mLock);
+ if (!mIdleWaiter) {
+ mIdleWaiter = std::make_shared<IdleWaiter>();
+ }
+
+ // Return a lambda that holds a strong reference to the idle waiter, whose lifetime can extend
+ // beyond this threaded backend object.
+ return [idleWaiter = mIdleWaiter]() {
+ std::unique_lock idleLock(idleWaiter->idleLock);
+ base::ScopedLockAssertion assumeLocked(idleWaiter->idleLock);
+ idleWaiter->threadIdleCondition.wait(idleLock, [&]() REQUIRES(idleWaiter->idleLock) {
+ return idleWaiter->isIdle;
+ });
+ };
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::setIdleStatus(bool isIdle) {
+ if (!mIdleWaiter) {
+ return;
+ }
+ std::scoped_lock idleLock(mIdleWaiter->idleLock);
+ mIdleWaiter->isIdle = isIdle;
+ if (isIdle) {
+ mIdleWaiter->threadIdleCondition.notify_all();
+ }
+}
+
// Explicit template instantiation for the PerfettoBackend.
template class ThreadedBackend<PerfettoBackend>;
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
index 650a87e452..52a84c470c 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
@@ -42,6 +42,9 @@ public:
void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override;
void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override;
+ /** Returns a function that, when called, will block until the tracing thread is idle. */
+ std::function<void()> getIdleWaiterForTesting();
+
private:
std::mutex mLock;
bool mThreadExit GUARDED_BY(mLock){false};
@@ -52,12 +55,21 @@ private:
TracedEventMetadata>;
std::vector<TraceEntry> mQueue GUARDED_BY(mLock);
+ struct IdleWaiter {
+ std::mutex idleLock;
+ std::condition_variable threadIdleCondition;
+ bool isIdle GUARDED_BY(idleLock){false};
+ };
+ // The lazy-initialized object used to wait for the tracing thread to idle.
+ std::shared_ptr<IdleWaiter> mIdleWaiter GUARDED_BY(mLock);
+
// InputThread stops when its destructor is called. Initialize it last so that it is the
// first thing to be destructed. This will guarantee the thread will not access other
// members that have already been destructed.
InputThread mTracerThread;
void threadLoop();
+ void setIdleStatus(bool isIdle) REQUIRES(mLock);
};
} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h
index 8ffbc11a13..1bd55958d9 100644
--- a/services/inputflinger/include/NotifyArgsBuilders.h
+++ b/services/inputflinger/include/NotifyArgsBuilders.h
@@ -30,8 +30,11 @@ namespace android {
class MotionArgsBuilder {
public:
- MotionArgsBuilder(int32_t action, int32_t source) {
+ MotionArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) {
mAction = action;
+ if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
+ addFlag(AMOTION_EVENT_FLAG_CANCELED);
+ }
mSource = source;
mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
mDownTime = mEventTime;
@@ -97,7 +100,7 @@ public:
return *this;
}
- NotifyMotionArgs build() {
+ NotifyMotionArgs build() const {
std::vector<PointerProperties> pointerProperties;
std::vector<PointerCoords> pointerCoords;
for (const PointerBuilder& pointer : mPointers) {
@@ -106,19 +109,17 @@ public:
}
// Set mouse cursor position for the most common cases to avoid boilerplate.
+ float resolvedCursorX = mRawXCursorPosition;
+ float resolvedCursorY = mRawYCursorPosition;
if (mSource == AINPUT_SOURCE_MOUSE &&
!MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_X) &&
BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_Y)) {
- mRawXCursorPosition = pointerCoords[0].getX();
- mRawYCursorPosition = pointerCoords[0].getY();
- }
-
- if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
- addFlag(AMOTION_EVENT_FLAG_CANCELED);
+ resolvedCursorX = pointerCoords[0].getX();
+ resolvedCursorY = pointerCoords[0].getY();
}
- return {InputEvent::nextId(),
+ return {mEventId,
mEventTime,
/*readTime=*/mEventTime,
mDeviceId,
@@ -137,13 +138,14 @@ public:
pointerCoords.data(),
/*xPrecision=*/0,
/*yPrecision=*/0,
- mRawXCursorPosition,
- mRawYCursorPosition,
+ resolvedCursorX,
+ resolvedCursorY,
mDownTime,
/*videoFrames=*/{}};
}
private:
+ const int32_t mEventId;
int32_t mAction;
int32_t mDeviceId{DEFAULT_DEVICE_ID};
uint32_t mSource;
@@ -163,7 +165,7 @@ private:
class KeyArgsBuilder {
public:
- KeyArgsBuilder(int32_t action, int32_t source) {
+ KeyArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) {
mAction = action;
mSource = source;
mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -206,7 +208,7 @@ public:
}
NotifyKeyArgs build() const {
- return {InputEvent::nextId(),
+ return {mEventId,
mEventTime,
/*readTime=*/mEventTime,
mDeviceId,
@@ -222,6 +224,7 @@ public:
}
private:
+ const int32_t mEventId;
int32_t mAction;
int32_t mDeviceId = DEFAULT_DEVICE_ID;
uint32_t mSource;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 7d27d4a9ce..8d915996f7 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -20,8 +20,24 @@
#include "TouchInputMapper.h"
+#include <algorithm>
+#include <cinttypes>
+#include <cmath>
+#include <cstddef>
+#include <tuple>
+
+#include <math.h>
+
+#include <android-base/stringprintf.h>
+#include <android/input.h>
#include <ftl/enum.h>
#include <input/PrintTools.h>
+#include <input/PropertyMap.h>
+#include <input/VirtualKeyMap.h>
+#include <linux/input-event-codes.h>
+#include <log/log_main.h>
+#include <math/vec2.h>
+#include <ui/FloatRect.h>
#include "CursorButtonAccumulator.h"
#include "CursorScrollAccumulator.h"
@@ -147,20 +163,6 @@ void TouchInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
info.addMotionRange(mOrientedRanges.y);
info.addMotionRange(mOrientedRanges.pressure);
- if (mDeviceMode == DeviceMode::UNSCALED && mSource == AINPUT_SOURCE_TOUCHPAD) {
- // Populate RELATIVE_X and RELATIVE_Y motion ranges for touchpad capture mode.
- //
- // RELATIVE_X and RELATIVE_Y motion ranges should be the largest possible relative
- // motion, i.e. the hardware dimensions, as the finger could move completely across the
- // touchpad in one sample cycle.
- const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
- const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
- info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, x.fuzz,
- x.resolution);
- info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, y.fuzz,
- y.resolution);
- }
-
if (mOrientedRanges.size) {
info.addMotionRange(*mOrientedRanges.size);
}
@@ -531,7 +533,7 @@ bool TouchInputMapper::hasExternalStylus() const {
* 4. Otherwise, use a non-display viewport.
*/
std::optional<DisplayViewport> TouchInputMapper::findViewport() {
- if (mParameters.hasAssociatedDisplay && mDeviceMode != DeviceMode::UNSCALED) {
+ if (mParameters.hasAssociatedDisplay) {
if (getDeviceContext().getAssociatedViewport()) {
return getDeviceContext().getAssociatedViewport();
}
@@ -939,8 +941,10 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded)
mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
mDeviceMode = DeviceMode::NAVIGATION;
} else {
- mSource = AINPUT_SOURCE_TOUCHPAD;
- mDeviceMode = DeviceMode::UNSCALED;
+ ALOGW("Touch device '%s' has invalid parameters or configuration. The device will be "
+ "inoperable.",
+ getDeviceName().c_str());
+ mDeviceMode = DeviceMode::DISABLED;
}
const std::optional<DisplayViewport> newViewportOpt = findViewport();
@@ -1884,8 +1888,7 @@ std::list<NotifyArgs> TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t
}
if (!mCurrentRawState.rawPointerData.hoveringIdBits.isEmpty() &&
- mCurrentRawState.rawPointerData.touchingIdBits.isEmpty() &&
- mDeviceMode != DeviceMode::UNSCALED) {
+ mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
// We have hovering pointers, and there are no touching pointers.
bool hoveringPointersInFrame = false;
auto hoveringIds = mCurrentRawState.rawPointerData.hoveringIdBits;
@@ -1912,7 +1915,7 @@ std::list<NotifyArgs> TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t
// Skip checking whether the pointer is inside the physical frame if the device is in
// unscaled or pointer mode.
if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) &&
- mDeviceMode != DeviceMode::UNSCALED && mDeviceMode != DeviceMode::POINTER) {
+ mDeviceMode != DeviceMode::POINTER) {
// If exactly one pointer went down, check for virtual key hit.
// Otherwise, we will drop the entire stroke.
if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 4b39e4099c..8451675944 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -16,17 +16,38 @@
#pragma once
+#include <array>
+#include <climits>
+#include <limits>
+#include <list>
+#include <memory>
#include <optional>
#include <string>
+#include <utility>
+#include <vector>
#include <stdint.h>
+#include <gui/constants.h>
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <input/InputDevice.h>
+#include <input/VelocityControl.h>
+#include <input/VelocityTracker.h>
+#include <ui/Rect.h>
#include <ui/Rotation.h>
+#include <ui/Size.h>
+#include <ui/Transform.h>
+#include <utils/BitSet.h>
+#include <utils/Timers.h>
#include "CursorButtonAccumulator.h"
#include "CursorScrollAccumulator.h"
#include "EventHub.h"
#include "InputMapper.h"
#include "InputReaderBase.h"
+#include "NotifyArgs.h"
+#include "PointerControllerInterface.h"
+#include "StylusState.h"
#include "TouchButtonAccumulator.h"
namespace android {
@@ -195,7 +216,6 @@ protected:
enum class DeviceMode {
DISABLED, // input is disabled
DIRECT, // direct mapping (touchscreen)
- UNSCALED, // unscaled mapping (e.g. captured touchpad)
NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
POINTER, // pointer mapping (e.g. uncaptured touchpad, drawing tablet)
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 721cdfdfb0..f558ba1196 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -185,6 +185,7 @@ private:
static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag,
AStatsEventList* outEventList,
void* cookie) {
+ ALOGI("Received pull request for touchpad usage atom");
LOG_ALWAYS_FATAL_IF(atomTag != android::util::TOUCHPAD_USAGE);
MetricsAccumulator& accumulator = MetricsAccumulator::getInstance();
accumulator.produceAtomsAndReset(*outEventList);
@@ -192,6 +193,7 @@ private:
}
void produceAtomsAndReset(AStatsEventList& outEventList) {
+ ALOGI("Acquiring lock for touchpad usage metrics...");
std::scoped_lock lock(mLock);
produceAtomsLocked(outEventList);
resetCountersLocked();
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
index a544fa36ae..6df339ed67 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -396,14 +396,16 @@ pub mod test_callbacks {
IInputThread::{BnInputThread, IInputThread, IInputThreadCallback::IInputThreadCallback},
KeyEvent::KeyEvent,
};
- use std::sync::{Arc, RwLock, RwLockWriteGuard};
+ use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+ use std::sync::{atomic::AtomicBool, atomic::Ordering, Arc, RwLock, RwLockWriteGuard};
+ use std::time::Duration;
#[derive(Default)]
struct TestCallbacksInner {
last_modifier_state: u32,
last_locked_modifier_state: u32,
last_event: Option<KeyEvent>,
- test_thread: Option<TestThread>,
+ test_thread: Option<FakeCppThread>,
}
#[derive(Default, Clone)]
@@ -438,13 +440,9 @@ pub mod test_callbacks {
self.0.read().unwrap().last_locked_modifier_state
}
- pub fn is_thread_created(&self) -> bool {
- self.0.read().unwrap().test_thread.is_some()
- }
-
- pub fn is_thread_finished(&self) -> bool {
+ pub fn is_thread_running(&self) -> bool {
if let Some(test_thread) = &self.0.read().unwrap().test_thread {
- return test_thread.is_finish_called();
+ return test_thread.is_running();
}
false
}
@@ -468,41 +466,101 @@ pub mod test_callbacks {
fn createInputFilterThread(
&self,
- _callback: &Strong<dyn IInputThreadCallback>,
+ callback: &Strong<dyn IInputThreadCallback>,
) -> std::result::Result<Strong<dyn IInputThread>, binder::Status> {
- let test_thread = TestThread::new();
+ let test_thread = FakeCppThread::new(callback.clone());
+ test_thread.start_looper();
self.inner().test_thread = Some(test_thread.clone());
Result::Ok(BnInputThread::new_binder(test_thread, BinderFeatures::default()))
}
}
#[derive(Default)]
- struct TestThreadInner {
- is_finish_called: bool,
+ struct FakeCppThreadInner {
+ join_handle: Option<std::thread::JoinHandle<()>>,
}
- #[derive(Default, Clone)]
- struct TestThread(Arc<RwLock<TestThreadInner>>);
+ #[derive(Clone)]
+ struct FakeCppThread {
+ callback: Arc<RwLock<Strong<dyn IInputThreadCallback>>>,
+ inner: Arc<RwLock<FakeCppThreadInner>>,
+ exit_flag: Arc<AtomicBool>,
+ }
- impl Interface for TestThread {}
+ impl Interface for FakeCppThread {}
- impl TestThread {
- pub fn new() -> Self {
- Default::default()
+ impl FakeCppThread {
+ pub fn new(callback: Strong<dyn IInputThreadCallback>) -> Self {
+ let thread = Self {
+ callback: Arc::new(RwLock::new(callback)),
+ inner: Arc::new(RwLock::new(FakeCppThreadInner { join_handle: None })),
+ exit_flag: Arc::new(AtomicBool::new(true)),
+ };
+ thread.create_looper();
+ thread
}
- fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> {
- self.0.write().unwrap()
+ fn inner(&self) -> RwLockWriteGuard<'_, FakeCppThreadInner> {
+ self.inner.write().unwrap()
+ }
+
+ fn create_looper(&self) {
+ let clone = self.clone();
+ let join_handle = std::thread::Builder::new()
+ .name("fake_cpp_thread".to_string())
+ .spawn(move || loop {
+ if !clone.exit_flag.load(Ordering::Relaxed) {
+ clone.loop_once();
+ }
+ })
+ .unwrap();
+ self.inner().join_handle = Some(join_handle);
+ // Sleep until the looper thread starts
+ std::thread::sleep(Duration::from_millis(10));
}
- pub fn is_finish_called(&self) -> bool {
- self.0.read().unwrap().is_finish_called
+ pub fn start_looper(&self) {
+ self.exit_flag.store(false, Ordering::Relaxed);
+ }
+
+ pub fn stop_looper(&self) {
+ self.exit_flag.store(true, Ordering::Relaxed);
+ if let Some(join_handle) = &self.inner.read().unwrap().join_handle {
+ join_handle.thread().unpark();
+ }
+ }
+
+ pub fn is_running(&self) -> bool {
+ !self.exit_flag.load(Ordering::Relaxed)
+ }
+
+ fn loop_once(&self) {
+ let _ = self.callback.read().unwrap().loopOnce();
}
}
- impl IInputThread for TestThread {
+ impl IInputThread for FakeCppThread {
fn finish(&self) -> binder::Result<()> {
- self.inner().is_finish_called = true;
+ self.stop_looper();
+ Result::Ok(())
+ }
+
+ fn wake(&self) -> binder::Result<()> {
+ if let Some(join_handle) = &self.inner.read().unwrap().join_handle {
+ join_handle.thread().unpark();
+ }
+ Result::Ok(())
+ }
+
+ fn sleepUntil(&self, wake_up_time: i64) -> binder::Result<()> {
+ let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+ if wake_up_time == i64::MAX {
+ std::thread::park();
+ } else {
+ let duration_now = Duration::from_nanos(now as u64);
+ let duration_wake_up = Duration::from_nanos(wake_up_time as u64);
+ std::thread::park_timeout(duration_wake_up - duration_now);
+ }
Result::Ok(())
}
}
diff --git a/services/inputflinger/rust/input_filter_thread.rs b/services/inputflinger/rust/input_filter_thread.rs
index 2d503aee70..96e5681ead 100644
--- a/services/inputflinger/rust/input_filter_thread.rs
+++ b/services/inputflinger/rust/input_filter_thread.rs
@@ -33,8 +33,6 @@ use com_android_server_inputflinger::aidl::com::android::server::inputflinger::I
use log::{debug, error};
use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
use std::sync::{Arc, RwLock, RwLockWriteGuard};
-use std::time::Duration;
-use std::{thread, thread::Thread};
/// Interface to receive callback from Input filter thread
pub trait ThreadCallback {
@@ -54,15 +52,18 @@ pub struct InputFilterThread {
thread_creator: InputFilterThreadCreator,
thread_callback_handler: ThreadCallbackHandler,
inner: Arc<RwLock<InputFilterThreadInner>>,
+ looper: Arc<RwLock<Looper>>,
}
struct InputFilterThreadInner {
- cpp_thread: Option<Strong<dyn IInputThread>>,
- looper: Option<Thread>,
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.
@@ -71,11 +72,10 @@ impl InputFilterThread {
thread_creator,
thread_callback_handler: ThreadCallbackHandler::new(),
inner: Arc::new(RwLock::new(InputFilterThreadInner {
- cpp_thread: None,
- looper: None,
next_timeout: i64::MAX,
is_finishing: false,
})),
+ looper: Arc::new(RwLock::new(Looper { cpp_thread: None })),
}
}
@@ -83,12 +83,17 @@ impl InputFilterThread {
/// time on the input filter thread.
/// {@see ThreadCallback.notify_timeout_expired(...)}
pub fn request_timeout_at_time(&self, when_nanos: i64) {
- let filter_thread = &mut self.filter_thread();
- if when_nanos < filter_thread.next_timeout {
- filter_thread.next_timeout = when_nanos;
- if let Some(looper) = &filter_thread.looper {
- looper.unpark();
+ 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();
}
}
@@ -120,29 +125,36 @@ impl InputFilterThread {
fn start(&self) {
debug!("InputFilterThread: start thread");
- let filter_thread = &mut self.filter_thread();
- if filter_thread.cpp_thread.is_none() {
- filter_thread.cpp_thread = Some(self.thread_creator.create(
- &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()),
- ));
- filter_thread.looper = None;
- filter_thread.is_finishing = false;
- }
+ {
+ // 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 = true;
- if let Some(looper) = &filter_thread.looper {
- looper.unpark();
- }
- if let Some(cpp_thread) = &filter_thread.cpp_thread {
- let _ = cpp_thread.finish();
- }
- // Clear all references
- filter_thread.cpp_thread = None;
- filter_thread.looper = None;
+ filter_thread.is_finishing = is_finishing;
}
fn loop_once(&self, now: i64) {
@@ -163,25 +175,34 @@ impl InputFilterThread {
wake_up_time = filter_thread.next_timeout;
}
}
- if filter_thread.looper.is_none() {
- filter_thread.looper = Some(std::thread::current());
- }
} // release thread lock
if timeout_expired {
self.thread_callback_handler.notify_timeout_expired(now);
}
- if wake_up_time == i64::MAX {
- thread::park();
- } else {
- let duration_now = Duration::from_nanos(now as u64);
- let duration_wake_up = Duration::from_nanos(wake_up_time as u64);
- thread::park_timeout(duration_wake_up - duration_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 {}
@@ -252,165 +273,64 @@ impl ThreadCallbackHandler {
#[cfg(test)]
mod tests {
- use crate::input_filter::test_callbacks::TestCallbacks;
- use crate::input_filter_thread::{
- test_thread::TestThread, test_thread_callback::TestThreadCallback,
- };
+ 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 = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let test_thread_callback = TestThreadCallback::new();
- test_thread.register_thread_callback(test_thread_callback);
- assert!(test_callbacks.is_thread_created());
+ 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 = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let test_thread_callback = TestThreadCallback::new();
- test_thread.register_thread_callback(test_thread_callback.clone());
- test_thread.unregister_thread_callback(test_thread_callback);
- assert!(test_callbacks.is_thread_finished());
+ 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 = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let test_thread_callback = TestThreadCallback::new();
- test_thread.register_thread_callback(test_thread_callback.clone());
- test_thread.start_looper();
+ test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
- test_thread.request_timeout_at_time(500);
- test_thread.dispatch_next();
+ let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds();
+ test_thread.request_timeout_at_time((now + 10) * 1000000);
- test_thread.move_time_forward(500);
-
- test_thread.stop_looper();
+ std::thread::sleep(Duration::from_millis(20));
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 = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let test_thread_callback = TestThreadCallback::new();
- test_thread.register_thread_callback(test_thread_callback.clone());
- test_thread.start_looper();
-
- test_thread.request_timeout_at_time(500);
- test_thread.dispatch_next();
+ test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
- test_thread.move_time_forward(100);
+ let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds();
+ test_thread.request_timeout_at_time((now + 100) * 1000000);
- test_thread.stop_looper();
+ std::thread::sleep(Duration::from_millis(10));
assert!(!test_thread_callback.is_notify_timeout_called());
}
-}
-
-#[cfg(test)]
-pub mod test_thread {
-
- use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator};
- use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread};
- use binder::Strong;
- use std::sync::{
- atomic::AtomicBool, atomic::AtomicI64, atomic::Ordering, Arc, RwLock, RwLockWriteGuard,
- };
- use std::time::Duration;
-
- #[derive(Clone)]
- pub struct TestThread {
- input_thread: InputFilterThread,
- inner: Arc<RwLock<TestThreadInner>>,
- exit_flag: Arc<AtomicBool>,
- now: Arc<AtomicI64>,
- }
-
- struct TestThreadInner {
- join_handle: Option<std::thread::JoinHandle<()>>,
- }
- impl TestThread {
- pub fn new(callbacks: TestCallbacks) -> TestThread {
- Self {
- input_thread: InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(
- RwLock::new(Strong::new(Box::new(callbacks))),
- ))),
- inner: Arc::new(RwLock::new(TestThreadInner { join_handle: None })),
- exit_flag: Arc::new(AtomicBool::new(false)),
- now: Arc::new(AtomicI64::new(0)),
- }
- }
-
- fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> {
- self.inner.write().unwrap()
- }
-
- pub fn get_input_thread(&self) -> InputFilterThread {
- self.input_thread.clone()
- }
-
- pub fn register_thread_callback(&self, thread_callback: TestThreadCallback) {
- self.input_thread.register_thread_callback(Box::new(thread_callback));
- }
-
- pub fn unregister_thread_callback(&self, thread_callback: TestThreadCallback) {
- self.input_thread.unregister_thread_callback(Box::new(thread_callback));
- }
-
- pub fn start_looper(&self) {
- self.exit_flag.store(false, Ordering::Relaxed);
- let clone = self.clone();
- let join_handle = std::thread::Builder::new()
- .name("test_thread".to_string())
- .spawn(move || {
- while !clone.exit_flag.load(Ordering::Relaxed) {
- clone.loop_once();
- }
- })
- .unwrap();
- self.inner().join_handle = Some(join_handle);
- // Sleep until the looper thread starts
- std::thread::sleep(Duration::from_millis(10));
- }
-
- pub fn stop_looper(&self) {
- self.exit_flag.store(true, Ordering::Relaxed);
- {
- let mut inner = self.inner();
- if let Some(join_handle) = &inner.join_handle {
- join_handle.thread().unpark();
- }
- inner.join_handle.take().map(std::thread::JoinHandle::join);
- inner.join_handle = None;
- }
- self.exit_flag.store(false, Ordering::Relaxed);
- }
-
- pub fn move_time_forward(&self, value: i64) {
- let _ = self.now.fetch_add(value, Ordering::Relaxed);
- self.dispatch_next();
- }
-
- pub fn dispatch_next(&self) {
- if let Some(join_handle) = &self.inner().join_handle {
- join_handle.thread().unpark();
- }
- // Sleep until the looper thread runs a loop
- std::thread::sleep(Duration::from_millis(10));
- }
-
- fn loop_once(&self) {
- self.input_thread.loop_once(self.now.load(Ordering::Relaxed));
- }
-
- pub fn request_timeout_at_time(&self, when_nanos: i64) {
- self.input_thread.request_timeout_at_time(when_nanos);
- }
+ fn get_thread(callbacks: TestCallbacks) -> InputFilterThread {
+ InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new(
+ Box::new(callbacks),
+ )))))
}
}
diff --git a/services/inputflinger/rust/slow_keys_filter.rs b/services/inputflinger/rust/slow_keys_filter.rs
index 09fbf40842..0f18a2f395 100644
--- a/services/inputflinger/rust/slow_keys_filter.rs
+++ b/services/inputflinger/rust/slow_keys_filter.rs
@@ -207,13 +207,19 @@ impl ThreadCallback for SlowKeysFilter {
#[cfg(test)]
mod tests {
- use crate::input_filter::{test_callbacks::TestCallbacks, test_filter::TestFilter, Filter};
- use crate::input_filter_thread::test_thread::TestThread;
+ use crate::input_filter::{
+ test_callbacks::TestCallbacks, test_filter::TestFilter, Filter, InputFilterThreadCreator,
+ };
+ use crate::input_filter_thread::InputFilterThread;
use crate::slow_keys_filter::{SlowKeysFilter, POLICY_FLAG_DISABLE_KEY_REPEAT};
use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+ use binder::Strong;
use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
};
+ use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+ use std::sync::{Arc, RwLock};
+ use std::time::Duration;
static BASE_KEY_EVENT: KeyEvent = KeyEvent {
id: 1,
@@ -231,18 +237,19 @@ mod tests {
metaState: 0,
};
+ static SLOW_KEYS_THRESHOLD_NS: i64 = 100 * 1000000; // 100 ms
+
#[test]
fn test_is_notify_key_for_internal_keyboard_not_blocked() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let next = TestFilter::new();
let mut filter = setup_filter_with_internal_device(
Box::new(next.clone()),
test_thread.clone(),
- 1, /* device_id */
- 100, /* threshold */
+ 1, /* device_id */
+ SLOW_KEYS_THRESHOLD_NS,
);
- test_thread.start_looper();
let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
filter.notify_key(&event);
@@ -252,15 +259,14 @@ mod tests {
#[test]
fn test_is_notify_key_for_external_stylus_not_blocked() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let next = TestFilter::new();
let mut filter = setup_filter_with_external_device(
Box::new(next.clone()),
test_thread.clone(),
- 1, /* device_id */
- 100, /* threshold */
+ 1, /* device_id */
+ SLOW_KEYS_THRESHOLD_NS,
);
- test_thread.start_looper();
let event =
KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
@@ -271,89 +277,115 @@ mod tests {
#[test]
fn test_notify_key_for_external_keyboard_when_key_pressed_for_threshold_time() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let next = TestFilter::new();
let mut filter = setup_filter_with_external_device(
Box::new(next.clone()),
test_thread.clone(),
- 1, /* device_id */
- 100, /* threshold */
+ 1, /* device_id */
+ SLOW_KEYS_THRESHOLD_NS,
);
- test_thread.start_looper();
-
- filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
+ let down_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+ filter.notify_key(&KeyEvent {
+ action: KeyEventAction::DOWN,
+ downTime: down_time,
+ eventTime: down_time,
+ ..BASE_KEY_EVENT
+ });
assert!(next.last_event().is_none());
- test_thread.dispatch_next();
- test_thread.move_time_forward(100);
-
- test_thread.stop_looper();
+ std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
assert_eq!(
next.last_event().unwrap(),
KeyEvent {
action: KeyEventAction::DOWN,
- downTime: 100,
- eventTime: 100,
+ downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+ eventTime: down_time + SLOW_KEYS_THRESHOLD_NS,
policyFlags: POLICY_FLAG_DISABLE_KEY_REPEAT,
..BASE_KEY_EVENT
}
);
+
+ let up_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+ filter.notify_key(&KeyEvent {
+ action: KeyEventAction::UP,
+ downTime: down_time,
+ eventTime: up_time,
+ ..BASE_KEY_EVENT
+ });
+
+ assert_eq!(
+ next.last_event().unwrap(),
+ KeyEvent {
+ action: KeyEventAction::UP,
+ downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+ eventTime: up_time,
+ ..BASE_KEY_EVENT
+ }
+ );
}
#[test]
fn test_notify_key_for_external_keyboard_when_key_not_pressed_for_threshold_time() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let next = TestFilter::new();
let mut filter = setup_filter_with_external_device(
Box::new(next.clone()),
test_thread.clone(),
- 1, /* device_id */
- 100, /* threshold */
+ 1, /* device_id */
+ SLOW_KEYS_THRESHOLD_NS,
);
- test_thread.start_looper();
-
- filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
- test_thread.dispatch_next();
-
- test_thread.move_time_forward(10);
-
- filter.notify_key(&KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT });
- test_thread.dispatch_next();
+ let mut now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+ filter.notify_key(&KeyEvent {
+ action: KeyEventAction::DOWN,
+ downTime: now,
+ eventTime: now,
+ ..BASE_KEY_EVENT
+ });
+
+ std::thread::sleep(Duration::from_nanos(SLOW_KEYS_THRESHOLD_NS as u64 / 2));
+
+ now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+ filter.notify_key(&KeyEvent {
+ action: KeyEventAction::UP,
+ downTime: now,
+ eventTime: now,
+ ..BASE_KEY_EVENT
+ });
- test_thread.stop_looper();
assert!(next.last_event().is_none());
}
#[test]
fn test_notify_key_for_external_keyboard_when_device_removed_before_threshold_time() {
let test_callbacks = TestCallbacks::new();
- let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread = get_thread(test_callbacks.clone());
let next = TestFilter::new();
let mut filter = setup_filter_with_external_device(
Box::new(next.clone()),
test_thread.clone(),
- 1, /* device_id */
- 100, /* threshold */
+ 1, /* device_id */
+ SLOW_KEYS_THRESHOLD_NS,
);
- test_thread.start_looper();
- filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
- assert!(next.last_event().is_none());
- test_thread.dispatch_next();
+ let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+ filter.notify_key(&KeyEvent {
+ action: KeyEventAction::DOWN,
+ downTime: now,
+ eventTime: now,
+ ..BASE_KEY_EVENT
+ });
filter.notify_devices_changed(&[]);
- test_thread.dispatch_next();
-
- test_thread.move_time_forward(100);
+ std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
- test_thread.stop_looper();
assert!(next.last_event().is_none());
}
fn setup_filter_with_external_device(
next: Box<dyn Filter + Send + Sync>,
- test_thread: TestThread,
+ test_thread: InputFilterThread,
device_id: i32,
threshold: i64,
) -> SlowKeysFilter {
@@ -367,7 +399,7 @@ mod tests {
fn setup_filter_with_internal_device(
next: Box<dyn Filter + Send + Sync>,
- test_thread: TestThread,
+ test_thread: InputFilterThread,
device_id: i32,
threshold: i64,
) -> SlowKeysFilter {
@@ -381,12 +413,18 @@ mod tests {
fn setup_filter_with_devices(
next: Box<dyn Filter + Send + Sync>,
- test_thread: TestThread,
+ test_thread: InputFilterThread,
devices: &[DeviceInfo],
threshold: i64,
) -> SlowKeysFilter {
- let mut filter = SlowKeysFilter::new(next, threshold, test_thread.get_input_thread());
+ let mut filter = SlowKeysFilter::new(next, threshold, test_thread);
filter.notify_devices_changed(devices);
filter
}
+
+ fn get_thread(callbacks: TestCallbacks) -> InputFilterThread {
+ InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new(
+ Box::new(callbacks),
+ )))))
+ }
}
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 6ae9790aac..9b5db23151 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -67,6 +67,8 @@ cc_test {
"InputProcessorConverter_test.cpp",
"InputDispatcher_test.cpp",
"InputReader_test.cpp",
+ "InputTraceSession.cpp",
+ "InputTracingTest.cpp",
"InstrumentedInputReader.cpp",
"LatencyTracker_test.cpp",
"MultiTouchMotionAccumulator_test.cpp",
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
index e231bccb79..1360cd0208 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -466,8 +466,15 @@ void FakeInputDispatcherPolicy::assertFilterInputEventWasCalledInternal(
mFilteredEvent = nullptr;
}
-gui::Uid FakeInputDispatcherPolicy::getPackageUid(std::string) {
- return gui::Uid::INVALID;
+gui::Uid FakeInputDispatcherPolicy::getPackageUid(std::string pkg) {
+ std::scoped_lock lock(mLock);
+ auto it = mPackageUidMap.find(pkg);
+ return it != mPackageUidMap.end() ? it->second : gui::Uid::INVALID;
+}
+
+void FakeInputDispatcherPolicy::addPackageUidMapping(std::string package, gui::Uid uid) {
+ std::scoped_lock lock(mLock);
+ mPackageUidMap.insert_or_assign(std::move(package), uid);
}
} // namespace android
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index d83924f202..2cc018ef05 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -115,6 +115,7 @@ public:
void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler);
void assertUnhandledKeyReported(int32_t keycode);
void assertUnhandledKeyNotReported();
+ void addPackageUidMapping(std::string package, gui::Uid uid);
private:
std::mutex mLock;
@@ -150,6 +151,8 @@ private:
std::queue<int32_t> mReportedUnhandledKeycodes GUARDED_BY(mLock);
std::function<std::optional<KeyEvent>(const KeyEvent&)> mUnhandledKeyHandler GUARDED_BY(mLock);
+ std::map<std::string, gui::Uid> mPackageUidMap GUARDED_BY(mLock);
+
/**
* All three ANR-related callbacks behave the same way, so we use this generic function to wait
* for a specific container to become non-empty. When the container is non-empty, return the
diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h
index c0c8975e76..26c2b4b1e7 100644
--- a/services/inputflinger/tests/FakeWindows.h
+++ b/services/inputflinger/tests/FakeWindows.h
@@ -157,6 +157,16 @@ public:
inline void setSpy(bool spy) { mInfo.setInputConfig(InputConfig::SPY, spy); }
+ inline void setSecure(bool secure) {
+ if (secure) {
+ mInfo.layoutParamsFlags |= gui::WindowInfo::Flag::SECURE;
+ } else {
+ using namespace ftl::flag_operators;
+ mInfo.layoutParamsFlags &= ~gui::WindowInfo::Flag::SECURE;
+ }
+ mInfo.setInputConfig(InputConfig::SENSITIVE_FOR_TRACING, secure);
+ }
+
inline void setInterceptsStylus(bool interceptsStylus) {
mInfo.setInputConfig(InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
}
@@ -229,10 +239,14 @@ public:
std::unique_ptr<KeyEvent> consumeKey(bool handled = true);
- inline void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
+ inline std::unique_ptr<KeyEvent> consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
std::unique_ptr<KeyEvent> keyEvent = consumeKey();
- ASSERT_NE(nullptr, keyEvent);
- ASSERT_THAT(*keyEvent, matcher);
+ EXPECT_NE(nullptr, keyEvent);
+ if (!keyEvent) {
+ return nullptr;
+ }
+ EXPECT_THAT(*keyEvent, matcher);
+ return keyEvent;
}
inline void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 1d46c9a1e2..367bc70a1c 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -9790,163 +9790,13 @@ TEST_F(MultiTouchInputMapperTest_ExternalDevice, Viewports_Fallback) {
ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId);
}
-TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) {
- // we need a pointer controller for mouse mode of touchpad (start pointer at 0,0)
+TEST_F(MultiTouchInputMapperTest, Process_TouchpadPointer) {
std::shared_ptr<FakePointerController> fakePointerController =
std::make_shared<FakePointerController>();
fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
fakePointerController->setPosition(0, 0);
- // prepare device and capture
- prepareDisplay(ui::ROTATION_0);
- prepareAxes(POSITION | ID | SLOT);
- mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
- mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
- mFakePolicy->setPointerCapture(/*window=*/sp<BBinder>::make());
- mFakePolicy->setPointerController(fakePointerController);
- MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
- // captured touchpad should be a touchpad source
- NotifyDeviceResetArgs resetArgs;
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
- ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
-
- InputDeviceInfo deviceInfo = mDevice->getDeviceInfo();
-
- const InputDeviceInfo::MotionRange* relRangeX =
- deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, AINPUT_SOURCE_TOUCHPAD);
- ASSERT_NE(relRangeX, nullptr);
- ASSERT_EQ(relRangeX->min, -(RAW_X_MAX - RAW_X_MIN));
- ASSERT_EQ(relRangeX->max, RAW_X_MAX - RAW_X_MIN);
- const InputDeviceInfo::MotionRange* relRangeY =
- deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, AINPUT_SOURCE_TOUCHPAD);
- ASSERT_NE(relRangeY, nullptr);
- ASSERT_EQ(relRangeY->min, -(RAW_Y_MAX - RAW_Y_MIN));
- ASSERT_EQ(relRangeY->max, RAW_Y_MAX - RAW_Y_MIN);
-
- // run captured pointer tests - note that this is unscaled, so input listener events should be
- // identical to what the hardware sends (accounting for any
- // calibration).
- // FINGER 0 DOWN
- processSlot(mapper, 0);
- processId(mapper, 1);
- processPosition(mapper, 100 + RAW_X_MIN, 100 + RAW_Y_MIN);
- processKey(mapper, BTN_TOUCH, 1);
- processSync(mapper);
-
- // expect coord[0] to contain initial location of touch 0
- NotifyMotionArgs args;
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
- ASSERT_EQ(1U, args.getPointerCount());
- ASSERT_EQ(0, args.pointerProperties[0].id);
- ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, args.source);
- ASSERT_NO_FATAL_FAILURE(
- assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0));
-
- // FINGER 1 DOWN
- processSlot(mapper, 1);
- processId(mapper, 2);
- processPosition(mapper, 560 + RAW_X_MIN, 154 + RAW_Y_MIN);
- processSync(mapper);
-
- // expect coord[0] to contain previous location, coord[1] to contain new touch 1 location
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action);
- ASSERT_EQ(2U, args.getPointerCount());
- ASSERT_EQ(0, args.pointerProperties[0].id);
- ASSERT_EQ(1, args.pointerProperties[1].id);
- ASSERT_NO_FATAL_FAILURE(
- assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0));
- ASSERT_NO_FATAL_FAILURE(
- assertPointerCoords(args.pointerCoords[1], 560, 154, 1, 0, 0, 0, 0, 0, 0, 0));
-
- // FINGER 1 MOVE
- processPosition(mapper, 540 + RAW_X_MIN, 690 + RAW_Y_MIN);
- processSync(mapper);
-
- // expect coord[0] to contain previous location, coord[1] to contain new touch 1 location
- // from move
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
- ASSERT_NO_FATAL_FAILURE(
- assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0));
- ASSERT_NO_FATAL_FAILURE(
- assertPointerCoords(args.pointerCoords[1], 540, 690, 1, 0, 0, 0, 0, 0, 0, 0));
-
- // FINGER 0 MOVE
- processSlot(mapper, 0);
- processPosition(mapper, 50 + RAW_X_MIN, 800 + RAW_Y_MIN);
- processSync(mapper);
-
- // expect coord[0] to contain new touch 0 location, coord[1] to contain previous location
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
- ASSERT_NO_FATAL_FAILURE(
- assertPointerCoords(args.pointerCoords[0], 50, 800, 1, 0, 0, 0, 0, 0, 0, 0));
- ASSERT_NO_FATAL_FAILURE(
- assertPointerCoords(args.pointerCoords[1], 540, 690, 1, 0, 0, 0, 0, 0, 0, 0));
-
- // BUTTON DOWN
- processKey(mapper, BTN_LEFT, 1);
- processSync(mapper);
-
- // touchinputmapper design sends a move before button press
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-
- // BUTTON UP
- processKey(mapper, BTN_LEFT, 0);
- processSync(mapper);
-
- // touchinputmapper design sends a move after button release
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-
- // FINGER 0 UP
- processId(mapper, -1);
- processSync(mapper);
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | 0x0000, args.action);
-
- // FINGER 1 MOVE
- processSlot(mapper, 1);
- processPosition(mapper, 320 + RAW_X_MIN, 900 + RAW_Y_MIN);
- processSync(mapper);
-
- // expect coord[0] to contain new location of touch 1, and properties[0].id to contain 1
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
- ASSERT_EQ(1U, args.getPointerCount());
- ASSERT_EQ(1, args.pointerProperties[0].id);
- ASSERT_NO_FATAL_FAILURE(
- assertPointerCoords(args.pointerCoords[0], 320, 900, 1, 0, 0, 0, 0, 0, 0, 0));
-
- // FINGER 1 UP
- processId(mapper, -1);
- processKey(mapper, BTN_TOUCH, 0);
- processSync(mapper);
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-
- // non captured touchpad should be a mouse source
- mFakePolicy->setPointerCapture(/*window=*/nullptr);
- configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
- ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
-}
-
-TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) {
- std::shared_ptr<FakePointerController> fakePointerController =
- std::make_shared<FakePointerController>();
- fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
- fakePointerController->setPosition(0, 0);
-
- // prepare device and capture
+ // prepare device
prepareDisplay(ui::ROTATION_0);
prepareAxes(POSITION | ID | SLOT);
mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
@@ -10004,7 +9854,7 @@ TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) {
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
}
-TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) {
+TEST_F(MultiTouchInputMapperTest, Touchpad_GetSources) {
std::shared_ptr<FakePointerController> fakePointerController =
std::make_shared<FakePointerController>();
@@ -10017,11 +9867,6 @@ TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) {
// uncaptured touchpad should be a pointer device
ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
-
- // captured touchpad should be a touchpad device
- mFakePolicy->setPointerCapture(/*window=*/sp<BBinder>::make());
- configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
- ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
}
// --- BluetoothMultiTouchInputMapperTest ---
diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp
new file mode 100644
index 0000000000..32acb5f288
--- /dev/null
+++ b/services/inputflinger/tests/InputTraceSession.cpp
@@ -0,0 +1,209 @@
+/*
+ * 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.
+ */
+
+#include "InputTraceSession.h"
+
+#include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/PrintTools.h>
+
+#include <utility>
+
+namespace android {
+
+using perfetto::protos::pbzero::AndroidInputEvent;
+using perfetto::protos::pbzero::AndroidInputEventConfig;
+using perfetto::protos::pbzero::AndroidKeyEvent;
+using perfetto::protos::pbzero::AndroidMotionEvent;
+using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent;
+
+// These operator<< definitions must be in the global namespace for them to be accessible to the
+// GTEST library. They cannot be in the anonymous namespace.
+static std::ostream& operator<<(std::ostream& out,
+ const std::variant<KeyEvent, MotionEvent>& event) {
+ std::visit([&](const auto& e) { out << e; }, event);
+ return out;
+}
+
+static std::ostream& operator<<(std::ostream& out,
+ const InputTraceSession::WindowDispatchEvent& event) {
+ out << "Window dispatch to windowId: " << event.window->getId() << ", event: " << event.event;
+ return out;
+}
+
+namespace {
+
+inline uint32_t getId(const std::variant<KeyEvent, MotionEvent>& event) {
+ return std::visit([&](const auto& e) { return e.getId(); }, event);
+}
+
+std::unique_ptr<perfetto::TracingSession> startTrace(
+ const std::function<void(protozero::HeapBuffered<AndroidInputEventConfig>&)>& configure) {
+ protozero::HeapBuffered<AndroidInputEventConfig> inputEventConfig{};
+ configure(inputEventConfig);
+
+ perfetto::TraceConfig config;
+ config.add_buffers()->set_size_kb(1024); // Record up to 1 MiB.
+ auto* dataSourceConfig = config.add_data_sources()->mutable_config();
+ dataSourceConfig->set_name("android.input.inputevent");
+ dataSourceConfig->set_android_input_event_config_raw(inputEventConfig.SerializeAsString());
+
+ std::unique_ptr<perfetto::TracingSession> tracingSession(perfetto::Tracing::NewTrace());
+ tracingSession->Setup(config);
+ tracingSession->StartBlocking();
+ return tracingSession;
+}
+
+std::string stopTrace(std::unique_ptr<perfetto::TracingSession> tracingSession) {
+ tracingSession->StopBlocking();
+ std::vector<char> traceChars(tracingSession->ReadTraceBlocking());
+ return {traceChars.data(), traceChars.size()};
+}
+
+// Decodes the trace, and returns all of the traced input events, and whether they were each
+// traced as a redacted event.
+auto decodeTrace(const std::string& rawTrace) {
+ using namespace perfetto::protos::pbzero;
+
+ ArrayMap<AndroidMotionEvent::Decoder, bool /*redacted*/> tracedMotions;
+ ArrayMap<AndroidKeyEvent::Decoder, bool /*redacted*/> tracedKeys;
+ ArrayMap<AndroidWindowInputDispatchEvent::Decoder, bool /*redacted*/> tracedWindowDispatches;
+
+ Trace::Decoder trace{rawTrace};
+ if (trace.has_packet()) {
+ auto it = trace.packet();
+ while (it) {
+ TracePacket::Decoder packet{it->as_bytes()};
+ if (packet.has_android_input_event()) {
+ AndroidInputEvent::Decoder event{packet.android_input_event()};
+ if (event.has_dispatcher_motion_event()) {
+ tracedMotions.emplace_back(event.dispatcher_motion_event(),
+ /*redacted=*/false);
+ }
+ if (event.has_dispatcher_motion_event_redacted()) {
+ tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(),
+ /*redacted=*/true);
+ }
+ if (event.has_dispatcher_key_event()) {
+ tracedKeys.emplace_back(event.dispatcher_key_event(),
+ /*redacted=*/false);
+ }
+ if (event.has_dispatcher_key_event_redacted()) {
+ tracedKeys.emplace_back(event.dispatcher_key_event_redacted(),
+ /*redacted=*/true);
+ }
+ if (event.has_dispatcher_window_dispatch_event()) {
+ tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(),
+ /*redacted=*/false);
+ }
+ if (event.has_dispatcher_window_dispatch_event_redacted()) {
+ tracedWindowDispatches
+ .emplace_back(event.dispatcher_window_dispatch_event_redacted(),
+ /*redacted=*/true);
+ }
+ }
+ it++;
+ }
+ }
+ return std::tuple{std::move(tracedMotions), std::move(tracedKeys),
+ std::move(tracedWindowDispatches)};
+}
+
+bool eventMatches(const MotionEvent& expected, const AndroidMotionEvent::Decoder& traced) {
+ return static_cast<uint32_t>(expected.getId()) == traced.event_id();
+}
+
+bool eventMatches(const KeyEvent& expected, const AndroidKeyEvent::Decoder& traced) {
+ return static_cast<uint32_t>(expected.getId()) == traced.event_id();
+}
+
+bool eventMatches(const InputTraceSession::WindowDispatchEvent& expected,
+ const AndroidWindowInputDispatchEvent::Decoder& traced) {
+ return static_cast<uint32_t>(getId(expected.event)) == traced.event_id() &&
+ expected.window->getId() == traced.window_id();
+}
+
+template <typename ExpectedEvents, typename TracedEvents>
+void verifyExpectedEventsTraced(const ExpectedEvents& expectedEvents,
+ const TracedEvents& tracedEvents, std::string_view name) {
+ uint32_t totalExpectedCount = 0;
+
+ for (const auto& [expectedEvent, expectedLevel] : expectedEvents) {
+ int32_t totalMatchCount = 0;
+ int32_t redactedMatchCount = 0;
+ for (const auto& [tracedEvent, isRedacted] : tracedEvents) {
+ if (eventMatches(expectedEvent, tracedEvent)) {
+ totalMatchCount++;
+ if (isRedacted) {
+ redactedMatchCount++;
+ }
+ }
+ }
+ switch (expectedLevel) {
+ case Level::NONE:
+ ASSERT_EQ(totalMatchCount, 0) << "Event should not be traced, but it was traced"
+ << "\n\tExpected event: " << expectedEvent;
+ break;
+ case Level::REDACTED:
+ case Level::COMPLETE:
+ ASSERT_EQ(totalMatchCount, 1)
+ << "Event should match exactly one traced event, but it matched: "
+ << totalMatchCount << "\n\tExpected event: " << expectedEvent;
+ ASSERT_EQ(redactedMatchCount, expectedLevel == Level::REDACTED ? 1 : 0);
+ totalExpectedCount++;
+ break;
+ }
+ }
+
+ ASSERT_EQ(tracedEvents.size(), totalExpectedCount)
+ << "The number of traced " << name
+ << " events does not exactly match the number of expected events";
+}
+
+} // namespace
+
+InputTraceSession::InputTraceSession(
+ std::function<void(protozero::HeapBuffered<AndroidInputEventConfig>&)> configure)
+ : mPerfettoSession(startTrace(std::move(configure))) {}
+
+InputTraceSession::~InputTraceSession() {
+ const auto rawTrace = stopTrace(std::move(mPerfettoSession));
+ verifyExpectations(rawTrace);
+}
+
+void InputTraceSession::expectMotionTraced(Level level, const MotionEvent& event) {
+ mExpectedMotions.emplace_back(event, level);
+}
+
+void InputTraceSession::expectKeyTraced(Level level, const KeyEvent& event) {
+ mExpectedKeys.emplace_back(event, level);
+}
+
+void InputTraceSession::expectDispatchTraced(Level level, const WindowDispatchEvent& event) {
+ mExpectedWindowDispatches.emplace_back(event, level);
+}
+
+void InputTraceSession::verifyExpectations(const std::string& rawTrace) {
+ auto [tracedMotions, tracedKeys, tracedWindowDispatches] = decodeTrace(rawTrace);
+
+ verifyExpectedEventsTraced(mExpectedMotions, tracedMotions, "motion");
+ verifyExpectedEventsTraced(mExpectedKeys, tracedKeys, "key");
+ verifyExpectedEventsTraced(mExpectedWindowDispatches, tracedWindowDispatches,
+ "window dispatch");
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputTraceSession.h b/services/inputflinger/tests/InputTraceSession.h
new file mode 100644
index 0000000000..ed20bc8343
--- /dev/null
+++ b/services/inputflinger/tests/InputTraceSession.h
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "FakeWindows.h"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <perfetto/config/android/android_input_event_config.pbzero.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/trace.pbzero.h>
+#include <perfetto/tracing.h>
+#include <variant>
+#include <vector>
+
+namespace android {
+
+/**
+ * Tracing level constants used for adding expectations to the InputTraceSession.
+ */
+enum class Level {
+ NONE,
+ REDACTED,
+ COMPLETE,
+};
+
+template <typename K, typename V>
+using ArrayMap = std::vector<std::pair<K, V>>;
+
+/**
+ * A scoped representation of a tracing session that is used to make assertions on the trace.
+ *
+ * When the trace session is created, an "android.input.inputevent" trace will be started
+ * synchronously with the given configuration. While the trace is ongoing, the caller must
+ * specify the events that are expected to be in the trace using the expect* methods.
+ *
+ * When the session is destroyed, the trace is stopped synchronously, and all expectations will
+ * be verified using the gtest framework. This acts as a strict verifier, where the verification
+ * will fail both if an expected event does not show up in the trace and if there is an extra
+ * event in the trace that was not expected. Ordering is NOT verified for any events.
+ */
+class InputTraceSession {
+public:
+ explicit InputTraceSession(
+ std::function<void(
+ protozero::HeapBuffered<perfetto::protos::pbzero::AndroidInputEventConfig>&)>
+ configure);
+
+ ~InputTraceSession();
+
+ void expectMotionTraced(Level level, const MotionEvent& event);
+
+ void expectKeyTraced(Level level, const KeyEvent& event);
+
+ struct WindowDispatchEvent {
+ std::variant<KeyEvent, MotionEvent> event;
+ sp<FakeWindowHandle> window;
+ };
+ void expectDispatchTraced(Level level, const WindowDispatchEvent& event);
+
+private:
+ std::unique_ptr<perfetto::TracingSession> mPerfettoSession;
+ ArrayMap<WindowDispatchEvent, Level> mExpectedWindowDispatches;
+ ArrayMap<MotionEvent, Level> mExpectedMotions;
+ ArrayMap<KeyEvent, Level> mExpectedKeys;
+
+ void verifyExpectations(const std::string& rawTrace);
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp
new file mode 100644
index 0000000000..fe4d6d9a8f
--- /dev/null
+++ b/services/inputflinger/tests/InputTracingTest.cpp
@@ -0,0 +1,732 @@
+/*
+ * 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.
+ */
+
+#include "../InputCommonConverter.h"
+#include "../dispatcher/InputDispatcher.h"
+#include "../dispatcher/trace/InputTracingPerfettoBackend.h"
+#include "../dispatcher/trace/ThreadedBackend.h"
+#include "FakeApplicationHandle.h"
+#include "FakeInputDispatcherPolicy.h"
+#include "FakeWindows.h"
+#include "InputTraceSession.h"
+#include "TestEventMatchers.h"
+
+#include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/trace.pbzero.h>
+#include <private/android_filesystem_config.h>
+#include <map>
+#include <vector>
+
+namespace android::inputdispatcher::trace {
+
+using perfetto::protos::pbzero::AndroidInputEventConfig;
+
+namespace {
+
+constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
+
+// Ensure common actions are interchangeable between keys and motions for convenience.
+static_assert(static_cast<int32_t>(AMOTION_EVENT_ACTION_DOWN) ==
+ static_cast<int32_t>(AKEY_EVENT_ACTION_DOWN));
+static_assert(static_cast<int32_t>(AMOTION_EVENT_ACTION_UP) ==
+ static_cast<int32_t>(AKEY_EVENT_ACTION_UP));
+constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
+constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP;
+constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL;
+
+constexpr gui::Pid PID{1};
+
+constexpr gui::Uid ALLOWED_UID_1{10012};
+constexpr gui::Uid ALLOWED_UID_2{10013};
+constexpr gui::Uid DISALLOWED_UID_1{1};
+constexpr gui::Uid DISALLOWED_UID_2{99};
+constexpr gui::Uid UNLISTED_UID{12345};
+
+const std::string ALLOWED_PKG_1{"allowed.pkg.1"};
+const std::string ALLOWED_PKG_2{"allowed.pkg.2"};
+const std::string DISALLOWED_PKG_1{"disallowed.pkg.1"};
+const std::string DISALLOWED_PKG_2{"disallowed.pkg.2"};
+
+const std::shared_ptr<FakeApplicationHandle> APP = std::make_shared<FakeApplicationHandle>();
+
+} // namespace
+
+// --- InputTracingTest ---
+
+class InputTracingTest : public testing::Test {
+protected:
+ std::unique_ptr<FakeInputDispatcherPolicy> mFakePolicy;
+ std::unique_ptr<InputDispatcher> mDispatcher;
+
+ void SetUp() override {
+ impl::PerfettoBackend::sUseInProcessBackendForTest = true;
+
+ mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
+ mFakePolicy->addPackageUidMapping(ALLOWED_PKG_1, ALLOWED_UID_1);
+ mFakePolicy->addPackageUidMapping(ALLOWED_PKG_2, ALLOWED_UID_2);
+ mFakePolicy->addPackageUidMapping(DISALLOWED_PKG_1, DISALLOWED_UID_1);
+ mFakePolicy->addPackageUidMapping(DISALLOWED_PKG_2, DISALLOWED_UID_2);
+
+ auto tracingBackend = std::make_unique<impl::ThreadedBackend<impl::PerfettoBackend>>(
+ impl::PerfettoBackend([this](const auto& pkg) {
+ return static_cast<InputDispatcherPolicyInterface&>(*mFakePolicy)
+ .getPackageUid(pkg);
+ }));
+ mRequestTracerIdle = tracingBackend->getIdleWaiterForTesting();
+ mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy, std::move(tracingBackend));
+
+ mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
+ ASSERT_EQ(OK, mDispatcher->start());
+ }
+
+ void TearDown() override {
+ ASSERT_EQ(OK, mDispatcher->stop());
+ mDispatcher.reset();
+ mFakePolicy.reset();
+ }
+
+ void waitForTracerIdle() {
+ mDispatcher->waitForIdle();
+ mRequestTracerIdle();
+ }
+
+ void setFocusedWindow(const sp<gui::WindowInfoHandle>& window) {
+ gui::FocusRequest request;
+ request.token = window->getToken();
+ request.windowName = window->getName();
+ request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
+ request.displayId = window->getInfo()->displayId;
+ mDispatcher->setFocusedWindow(request);
+ }
+
+ void tapAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows,
+ Level inboundTraceLevel, Level dispatchTraceLevel, InputTraceSession& s) {
+ const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ mDispatcher->notifyMotion(down);
+ s.expectMotionTraced(inboundTraceLevel, toMotionEvent(down));
+ for (const auto& window : windows) {
+ auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+ }
+
+ const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ mDispatcher->notifyMotion(up);
+ s.expectMotionTraced(inboundTraceLevel, toMotionEvent(up));
+ for (const auto& window : windows) {
+ auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+ s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+ }
+ }
+
+ void keypressAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows,
+ Level inboundTraceLevel, Level dispatchTraceLevel,
+ InputTraceSession& s) {
+ const auto down = KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build();
+ mDispatcher->notifyKey(down);
+ s.expectKeyTraced(inboundTraceLevel, toKeyEvent(down));
+ for (const auto& window : windows) {
+ auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_DOWN));
+ s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+ }
+
+ const auto up = KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build();
+ mDispatcher->notifyKey(up);
+ s.expectKeyTraced(inboundTraceLevel, toKeyEvent(up));
+ for (const auto& window : windows) {
+ auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_UP));
+ s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+ }
+ }
+
+private:
+ std::function<void()> mRequestTracerIdle;
+};
+
+TEST_F(InputTracingTest, EmptyConfigTracesNothing) {
+ InputTraceSession s{[](auto& config) {}};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true);
+
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+ keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceAll) {
+ InputTraceSession s{
+ [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true);
+
+ tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+ keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, NoRulesTracesNothing) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true);
+
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+ keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, EmptyRuleMatchesEverything) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match everything as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true);
+
+ tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+ keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, UnspecifiedTracelLevel) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match everything, trace level unspecified
+ auto rule = config->add_rules();
+ }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true);
+
+ // Event is not traced by default if trace level is unspecified
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+ keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchSecureWindow) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match secure windows as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule->set_match_secure(true);
+ }};
+
+ // Add a normal window and a spy window.
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ spy->setSpy(true);
+ spy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // Since neither are secure windows, events should not be traced.
+ tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+ // Events should be matched as secure if any of the target windows is marked as secure.
+ spy->setSecure(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+ tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ spy->setSecure(false);
+ window->setSecure(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+ tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ spy->setSecure(true);
+ window->setSecure(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+ tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ spy->setSecure(false);
+ window->setSecure(false);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+ tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchImeConnectionActive) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match IME Connection Active as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule->set_match_ime_connection_active(true);
+ }};
+
+ // Add a normal window and a spy window.
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ spy->setSpy(true);
+ spy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // Since IME connection is not active, events should not be traced.
+ tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+ mDispatcher->setInputMethodConnectionIsActive(true);
+ tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ mDispatcher->setInputMethodConnectionIsActive(false);
+ tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchAllPackages) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match all package as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule->add_match_all_packages(ALLOWED_PKG_1);
+ rule->add_match_all_packages(ALLOWED_PKG_2);
+ }};
+
+ // All windows are allowlisted.
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ window->setOwnerInfo(PID, ALLOWED_UID_1);
+ auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ spy->setOwnerInfo(PID, ALLOWED_UID_2);
+ spy->setSpy(true);
+ spy->setTrustedOverlay(true);
+ auto systemSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ systemSpy->setOwnerInfo(PID, gui::Uid{AID_SYSTEM});
+ systemSpy->setSpy(true);
+ systemSpy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged(
+ {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ // Add a disallowed spy. This will result in the event not being traced for all windows.
+ auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1);
+ disallowedSpy->setSpy(true);
+ disallowedSpy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(),
+ *disallowedSpy->getInfo(), *window->getInfo()},
+ {},
+ 0,
+ 0});
+
+ tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+ // Change the owner of the disallowed spy to one for which we don't have a package mapping.
+ disallowedSpy->setOwnerInfo(PID, UNLISTED_UID);
+ mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(),
+ *disallowedSpy->getInfo(), *window->getInfo()},
+ {},
+ 0,
+ 0});
+
+ tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+ // Remove the disallowed spy. Events are traced again.
+ mDispatcher->onWindowInfosChanged(
+ {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchAnyPackages) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match any package as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule->add_match_any_packages(ALLOWED_PKG_1);
+ rule->add_match_any_packages(ALLOWED_PKG_2);
+ }};
+
+ // Just a disallowed window. Events are not traced.
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ window->setOwnerInfo(PID, DISALLOWED_UID_1);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ // Add a spy for which we don't have a package mapping. Events are still not traced.
+ auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ disallowedSpy->setOwnerInfo(PID, UNLISTED_UID);
+ disallowedSpy->setSpy(true);
+ disallowedSpy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+ // Add an allowed spy. Events are now traced for all packages.
+ auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ spy->setOwnerInfo(PID, ALLOWED_UID_1);
+ spy->setSpy(true);
+ spy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged(
+ {{*disallowedSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({disallowedSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ // Add another disallowed spy. Events are still traced.
+ auto disallowedSpy2 = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ disallowedSpy2->setOwnerInfo(PID, DISALLOWED_UID_2);
+ disallowedSpy2->setSpy(true);
+ disallowedSpy2->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *disallowedSpy2->getInfo(),
+ *spy->getInfo(), *window->getInfo()},
+ {},
+ 0,
+ 0});
+
+ tapAndExpect({disallowedSpy, disallowedSpy2, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MultipleMatchersInOneRule) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Match all of the following conditions as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule->add_match_all_packages(ALLOWED_PKG_1);
+ rule->add_match_all_packages(ALLOWED_PKG_2);
+ rule->add_match_any_packages(ALLOWED_PKG_1);
+ rule->add_match_any_packages(DISALLOWED_PKG_1);
+ rule->set_match_secure(false);
+ rule->set_match_ime_connection_active(false);
+ }};
+
+ // A single window into an allowed UID. Matches all matchers.
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ window->setOwnerInfo(PID, ALLOWED_UID_1);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ // Secure window does not match.
+ window->setSecure(true);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ // IME Connection Active does not match.
+ window->setSecure(false);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ mDispatcher->setInputMethodConnectionIsActive(true);
+
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ // Event going to DISALLOWED_PKG_1 does not match because it's not listed in match_all_packages.
+ mDispatcher->setInputMethodConnectionIsActive(false);
+ auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1);
+ disallowedSpy->setSpy(true);
+ disallowedSpy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+ // Event going to ALLOWED_PKG_1 does not match because it's not listed in match_any_packages.
+ window->setOwnerInfo(PID, ALLOWED_UID_2);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+ // All conditions match.
+ auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ spy->setOwnerInfo(PID, ALLOWED_UID_1);
+ spy->setSpy(true);
+ spy->setTrustedOverlay(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MultipleRulesMatchInOrder) {
+ InputTraceSession s{[](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Don't trace secure events
+ auto rule1 = config->add_rules();
+ rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_NONE);
+ rule1->set_match_secure(true);
+ // Rule: Trace matched packages as COMPLETE when IME inactive
+ auto rule2 = config->add_rules();
+ rule2->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule2->add_match_all_packages(ALLOWED_PKG_1);
+ rule2->add_match_all_packages(ALLOWED_PKG_2);
+ rule2->set_match_ime_connection_active(false);
+ // Rule: Trace the rest of the events as REDACTED
+ auto rule3 = config->add_rules();
+ rule3->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+ }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ window->setOwnerInfo(PID, ALLOWED_UID_1);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ // Verify that the first rule that matches in the order that they are specified is the
+ // one that applies to the event.
+ mDispatcher->setInputMethodConnectionIsActive(true);
+ tapAndExpect({window}, Level::REDACTED, Level::REDACTED, s);
+
+ mDispatcher->setInputMethodConnectionIsActive(false);
+ auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+ spy->setOwnerInfo(PID, ALLOWED_UID_2);
+ spy->setSpy(true);
+ spy->setTrustedOverlay(true);
+ spy->setSecure(true);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+ spy->setSecure(false);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+ spy->setOwnerInfo(PID, DISALLOWED_UID_1);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ tapAndExpect({spy, window}, Level::REDACTED, Level::REDACTED, s);
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceInboundEvents) {
+ InputTraceSession s{[](auto& config) {
+ // Only trace inbounds events - don't trace window dispatch
+ config->set_trace_dispatcher_input_events(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Trace everything as REDACTED
+ auto rule1 = config->add_rules();
+ rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+ }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ window->setOwnerInfo(PID, ALLOWED_UID_1);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ // Only the inbound events are traced. No dispatch events are traced.
+ tapAndExpect({window}, Level::REDACTED, Level::NONE, s);
+
+ // Notify a down event, which should be traced.
+ const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ s.expectMotionTraced(Level::REDACTED, toMotionEvent(down));
+ mDispatcher->notifyMotion(down);
+ auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ s.expectDispatchTraced(Level::NONE, {*consumed, window});
+
+ // Force a cancel event to be synthesized. This should not be traced, because only inbound
+ // events are requested.
+ mDispatcher->cancelCurrentTouch();
+ consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+ s.expectMotionTraced(Level::NONE, *consumed);
+ s.expectDispatchTraced(Level::NONE, {*consumed, window});
+
+ waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceWindowDispatch) {
+ InputTraceSession s{[](auto& config) {
+ // Only trace window dispatch - don't trace event details
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Trace everything as REDACTED
+ auto rule1 = config->add_rules();
+ rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+ }};
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ window->setOwnerInfo(PID, ALLOWED_UID_1);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ // Only dispatch events are traced. No inbound events are traced.
+ tapAndExpect({window}, Level::NONE, Level::REDACTED, s);
+
+ // Notify a down event; the dispatch should be traced.
+ const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ s.expectMotionTraced(Level::NONE, toMotionEvent(down));
+ mDispatcher->notifyMotion(down);
+ auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ s.expectDispatchTraced(Level::REDACTED, {*consumed, window});
+
+ // Force a cancel event to be synthesized. All events that are dispatched should be traced.
+ mDispatcher->cancelCurrentTouch();
+ consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+ s.expectMotionTraced(Level::NONE, *consumed);
+ s.expectDispatchTraced(Level::REDACTED, {*consumed, window});
+}
+
+TEST_F(InputTracingTest, SimultaneousTracingSessions) {
+ auto s1 = std::make_unique<InputTraceSession>(
+ [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); });
+
+ auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true);
+
+ tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+ keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+
+ auto s2 = std::make_unique<InputTraceSession>([](auto& config) {
+ config->set_trace_dispatcher_input_events(true);
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Trace all events as REDACTED when IME inactive
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+ rule->set_match_ime_connection_active(false);
+ });
+
+ auto s3 = std::make_unique<InputTraceSession>([](auto& config) {
+ // Only trace window dispatch
+ config->set_trace_dispatcher_window_dispatch(true);
+ config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+ // Rule: Trace non-secure events as COMPLETE
+ auto rule = config->add_rules();
+ rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+ rule->set_match_secure(false);
+ });
+
+ // Down event should be recorded on all traces.
+ const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ mDispatcher->notifyMotion(down);
+ s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(down));
+ s2->expectMotionTraced(Level::REDACTED, toMotionEvent(down));
+ s3->expectMotionTraced(Level::NONE, toMotionEvent(down));
+ auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+ s2->expectDispatchTraced(Level::REDACTED, {*consumed, window});
+ s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+ // Move event when IME is active.
+ mDispatcher->setInputMethodConnectionIsActive(true);
+ const auto move1 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ mDispatcher->notifyMotion(move1);
+ s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move1));
+ s2->expectMotionTraced(Level::NONE, toMotionEvent(move1));
+ s3->expectMotionTraced(Level::NONE, toMotionEvent(move1));
+ consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+ s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+ s2->expectDispatchTraced(Level::NONE, {*consumed, window});
+ s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+ // Move event after window became secure.
+ mDispatcher->setInputMethodConnectionIsActive(false);
+ window->setSecure(true);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ const auto move2 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ mDispatcher->notifyMotion(move2);
+ s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move2));
+ s2->expectMotionTraced(Level::REDACTED, toMotionEvent(move2));
+ s3->expectMotionTraced(Level::NONE, toMotionEvent(move2));
+ consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+ s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+ s2->expectDispatchTraced(Level::REDACTED, {*consumed, window});
+ s3->expectDispatchTraced(Level::NONE, {*consumed, window});
+
+ waitForTracerIdle();
+ s2.reset();
+
+ // Up event.
+ window->setSecure(false);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+ .build();
+ mDispatcher->notifyMotion(up);
+ s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(up));
+ s3->expectMotionTraced(Level::NONE, toMotionEvent(up));
+ consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+ s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+ s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+ waitForTracerIdle();
+ s3.reset();
+
+ tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+ keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+
+ waitForTracerIdle();
+ s1.reset();
+}
+
+} // namespace android::inputdispatcher::trace
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
index 0e3fdbb0dc..10dc9276d2 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <common/FlagManager.h>
#include <compositionengine/impl/planner/LayerState.h>
namespace {
@@ -70,6 +71,10 @@ size_t LayerState::getHash() const {
if (field->getField() == LayerStateField::Buffer) {
continue;
}
+ if (FlagManager::getInstance().cache_when_source_crop_layer_only_moved() &&
+ field->getField() == LayerStateField::SourceCrop) {
+ continue;
+ }
android::hashCombineSingleHashed(hash, field->getHash());
}
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
index 39fce2b629..03758b345a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "LayerStateTest"
#include <aidl/android/hardware/graphics/common/BufferUsage.h>
+#include <common/include/common/test/FlagUtils.h>
#include <compositionengine/impl/OutputLayer.h>
#include <compositionengine/impl/planner/LayerState.h>
#include <compositionengine/mock/LayerFE.h>
@@ -26,6 +27,7 @@
#include <log/log.h>
#include "android/hardware_buffer.h"
+#include "com_android_graphics_surfaceflinger_flags.h"
#include "compositionengine/LayerFECompositionState.h"
#include <aidl/android/hardware/graphics/composer3/Composition.h>
@@ -464,6 +466,9 @@ TEST_F(LayerStateTest, updateSourceCrop) {
}
TEST_F(LayerStateTest, compareSourceCrop) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
OutputLayerCompositionState outputLayerCompositionState;
outputLayerCompositionState.sourceCrop = sFloatRectOne;
LayerFECompositionState layerFECompositionState;
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
index 35d0ffb6e9..a1210b4e21 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
@@ -18,6 +18,9 @@
#undef LOG_TAG
#define LOG_TAG "PredictorTest"
+#include <common/include/common/test/FlagUtils.h>
+#include "com_android_graphics_surfaceflinger_flags.h"
+
#include <compositionengine/impl/planner/Predictor.h>
#include <compositionengine/mock/LayerFE.h>
#include <compositionengine/mock/OutputLayer.h>
@@ -127,6 +130,9 @@ TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchDifferentCompositionTypes
}
TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInSingleLayer) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -158,6 +164,9 @@ TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInSingleLayer)
}
TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInMultiLayerStack) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -304,6 +313,9 @@ TEST_F(LayerStackTest, getApproximateMatch_alwaysMatchesClientComposition) {
}
TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchMultipleApproximations) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -347,6 +359,9 @@ struct PredictionTest : public testing::Test {
};
TEST_F(LayerStackTest, reorderingChangesNonBufferHash) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -467,6 +482,9 @@ TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveExactMatch) {
}
TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveApproximateMatch) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -504,6 +522,9 @@ TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveApproximateMatc
}
TEST_F(PredictorTest, recordMissedPlan_skipsApproximateMatch) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_when_source_crop_layer_only_moved,
+ false);
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
OutputLayerCompositionState outputLayerCompositionStateOne{
diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS
index 0aee7d497c..ffc1dd7979 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -2,7 +2,6 @@
adyabr@google.com
alecmouri@google.com
-chaviw@google.com
domlaskowski@google.com
jreck@google.com
lpy@google.com
@@ -10,5 +9,6 @@ pdwilliams@google.com
racarr@google.com
ramindani@google.com
rnlee@google.com
+sallyqi@google.com
scroggo@google.com
vishnun@google.com
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 7b313c3c99..21f1cb3e4a 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -72,6 +72,7 @@
#include <gui/TraceUtils.h>
#include <hidl/ServiceManagement.h>
#include <layerproto/LayerProtoParser.h>
+#include <linux/sched/types.h>
#include <log/log.h>
#include <private/android_filesystem_config.h>
#include <private/gui/SyncFeatures.h>
@@ -5252,17 +5253,19 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin
desiredPresentTime, isAutoTimestamp,
postTime, transactionId);
}
- if ((flags & eAnimation) && resolvedState.state.surface) {
- if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
- const auto layerProps = scheduler::LayerProps{
- .visible = layer->isVisible(),
- .bounds = layer->getBounds(),
- .transform = layer->getTransform(),
- .setFrameRateVote = layer->getFrameRateForLayerTree(),
- .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
- .isFrontBuffered = layer->isFrontBuffered(),
- };
- layer->recordLayerHistoryAnimationTx(layerProps, now);
+ if (!mLayerLifecycleManagerEnabled) {
+ if ((flags & eAnimation) && resolvedState.state.surface) {
+ if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
+ const auto layerProps = scheduler::LayerProps{
+ .visible = layer->isVisible(),
+ .bounds = layer->getBounds(),
+ .transform = layer->getTransform(),
+ .setFrameRateVote = layer->getFrameRateForLayerTree(),
+ .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
+ .isFrontBuffered = layer->isFrontBuffered(),
+ };
+ layer->recordLayerHistoryAnimationTx(layerProps, now);
+ }
}
}
}
@@ -7805,20 +7808,6 @@ status_t SurfaceFlinger::setSchedAttr(bool enabled) {
return NO_ERROR;
}
- // Currently, there is no wrapper in bionic: b/183240349.
- struct sched_attr {
- uint32_t size;
- uint32_t sched_policy;
- uint64_t sched_flags;
- int32_t sched_nice;
- uint32_t sched_priority;
- uint64_t sched_runtime;
- uint64_t sched_deadline;
- uint64_t sched_period;
- uint32_t sched_util_min;
- uint32_t sched_util_max;
- };
-
sched_attr attr = {};
attr.size = sizeof(attr);
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
index 9b95d74899..15fde914f6 100644
--- a/services/vibratorservice/test/VibratorHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -255,16 +255,17 @@ TEST_F(VibratorHalControllerTest, TestScheduledCallbackSurvivesReconnection) {
.WillRepeatedly(Return(vibrator::HalResult<void>::transactionFailed("message")));
}
- std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
- auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+ auto counter = vibrator::TestCounter(0);
- auto onFn = [&](vibrator::HalWrapper* hal) { return hal->on(10ms, callback); };
+ auto onFn = [&](vibrator::HalWrapper* hal) {
+ return hal->on(10ms, [&counter] { counter.increment(); });
+ };
ASSERT_TRUE(mController->doWithRetry<void>(onFn, "on").isOk());
ASSERT_TRUE(mController->doWithRetry<void>(PING_FN, "ping").isFailed());
mMockHal.reset();
- ASSERT_EQ(0, *callbackCounter.get());
+ ASSERT_EQ(0, counter.get());
// Callback triggered even after HalWrapper was reconnected.
- std::this_thread::sleep_for(15ms);
- ASSERT_EQ(1, *callbackCounter.get());
+ counter.tryWaitUntilCountIsAtLeast(1, 500ms);
+ ASSERT_EQ(1, counter.get());
}
diff --git a/services/vibratorservice/test/test_utils.h b/services/vibratorservice/test/test_utils.h
index 1933a118ef..b584f64bbf 100644
--- a/services/vibratorservice/test/test_utils.h
+++ b/services/vibratorservice/test/test_utils.h
@@ -85,10 +85,36 @@ private:
~TestFactory() = delete;
};
+class TestCounter {
+public:
+ TestCounter(int32_t init) : mCount(init), mMutex(), mCondVar() {}
+
+ int32_t get() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ return mCount;
+ }
+
+ void increment() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mCount += 1;
+ mCondVar.notify_all();
+ }
+
+ void tryWaitUntilCountIsAtLeast(int32_t count, std::chrono::milliseconds timeout) {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mCondVar.wait_for(lock, timeout, [&] { return mCount >= count; });
+ }
+
+private:
+ int32_t mCount;
+ std::mutex mMutex;
+ std::condition_variable mCondVar;
+};
+
// -------------------------------------------------------------------------------------------------
} // namespace vibrator
} // namespace android
-#endif // VIBRATORSERVICE_UNITTEST_UTIL_H_ \ No newline at end of file
+#endif // VIBRATORSERVICE_UNITTEST_UTIL_H_