Merge "Dump OverlayProperties" into main
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index 341fabb..de0aafa 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -37,9 +37,6 @@
     name: "libdumpstateutil",
     defaults: ["dumpstate_cflag_defaults"],
     vendor_available: true,
-    vndk: {
-        enabled: true,
-    },
     srcs: [
         "DumpstateInternal.cpp",
         "DumpstateUtil.cpp",
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index a5c0c60..95a05cd 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -790,7 +790,8 @@
 
     if (OK !=
         IInterface::asBinder(cb)->linkToDeath(sp<ServiceManager>::fromExisting(this))) {
-        ALOGE("%s Could not linkToDeath when adding client callback for %s", name.c_str());
+        ALOGE("%s Could not linkToDeath when adding client callback for %s",
+              ctx.toDebugString().c_str(), name.c_str());
         return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Couldn't linkToDeath.");
     }
 
diff --git a/libs/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h
index 7da8d51..04b7186 100644
--- a/libs/battery/MultiStateCounter.h
+++ b/libs/battery/MultiStateCounter.h
@@ -62,6 +62,12 @@
 
     void setState(state_t state, time_t timestamp);
 
+    /**
+     * Copies the current state and accumulated times-in-state from the source. Resets
+     * the accumulated value.
+     */
+    void copyStatesFrom(const MultiStateCounter<T>& source);
+
     void setValue(state_t state, const T& value);
 
     /**
@@ -193,6 +199,22 @@
 }
 
 template <class T>
+void MultiStateCounter<T>::copyStatesFrom(const MultiStateCounter<T>& source) {
+    if (stateCount != source.stateCount) {
+        ALOGE("State count mismatch: %u vs. %u\n", stateCount, source.stateCount);
+        return;
+    }
+
+    currentState = source.currentState;
+    for (int i = 0; i < stateCount; i++) {
+        states[i].timeInStateSinceUpdate = source.states[i].timeInStateSinceUpdate;
+        states[i].counter = emptyValue;
+    }
+    lastStateChangeTimestamp = source.lastStateChangeTimestamp;
+    lastUpdateTimestamp = source.lastUpdateTimestamp;
+}
+
+template <class T>
 void MultiStateCounter<T>::setValue(state_t state, const T& value) {
     states[state].counter = value;
 }
diff --git a/libs/battery/MultiStateCounterTest.cpp b/libs/battery/MultiStateCounterTest.cpp
index cb11a54..a51a38a 100644
--- a/libs/battery/MultiStateCounterTest.cpp
+++ b/libs/battery/MultiStateCounterTest.cpp
@@ -72,6 +72,22 @@
     EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2));
 }
 
+TEST_F(MultiStateCounterTest, copyStatesFrom) {
+    DoubleMultiStateCounter sourceCounter(3, 0);
+
+    sourceCounter.updateValue(0, 0);
+    sourceCounter.setState(1, 0);
+    sourceCounter.setState(2, 1000);
+
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.copyStatesFrom(sourceCounter);
+    testCounter.updateValue(6.0, 3000);
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2));
+}
+
 TEST_F(MultiStateCounterTest, setEnabled) {
     DoubleMultiStateCounter testCounter(3, 0);
     testCounter.updateValue(0, 0);
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index eec12e4..57a48d7 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -229,6 +229,7 @@
 cc_library_headers {
     name: "trusty_mock_headers",
     host_supported: true,
+    vendor_available: true,
 
     export_include_dirs: [
         "trusty/include",
@@ -243,12 +244,18 @@
 cc_defaults {
     name: "trusty_mock_defaults",
     host_supported: true,
+    vendor_available: true,
 
     header_libs: [
         "libbinder_headers_base",
         "liblog_stub",
         "trusty_mock_headers",
     ],
+    export_header_lib_headers: [
+        "libbinder_headers_base",
+        "liblog_stub",
+        "trusty_mock_headers",
+    ],
 
     shared_libs: [
         "libutils_binder_sdk",
@@ -358,9 +365,6 @@
 
     // for vndbinder
     vendor_available: true,
-    vndk: {
-        enabled: true,
-    },
     recovery_available: true,
     double_loadable: true,
     // TODO(b/153609531): remove when no longer needed.
@@ -669,6 +673,7 @@
         "//packages/modules/Virtualization:__subpackages__",
         "//device/google/cuttlefish/shared/minidroid:__subpackages__",
         "//system/software_defined_vehicle:__subpackages__",
+        "//visibility:any_system_partition",
     ],
 }
 
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index 2a8a353..9a2d14a 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -53,6 +53,7 @@
         "-DBINDER_WITH_KERNEL_IPC",
         "-Wall",
         "-Wextra",
+        "-Wextra-semi",
         "-Werror",
     ],
 
@@ -146,6 +147,46 @@
     afdo: true,
 }
 
+cc_library {
+    name: "libbinder_ndk_on_trusty_mock",
+    defaults: [
+        "trusty_mock_defaults",
+    ],
+
+    export_include_dirs: [
+        "include_cpp",
+        "include_ndk",
+        "include_platform",
+    ],
+
+    srcs: [
+        "ibinder.cpp",
+        "libbinder.cpp",
+        "parcel.cpp",
+        "stability.cpp",
+        "status.cpp",
+    ],
+
+    shared_libs: [
+        "libbinder_on_trusty_mock",
+    ],
+
+    header_libs: [
+        "libbinder_trusty_ndk_headers",
+    ],
+    export_header_lib_headers: [
+        "libbinder_trusty_ndk_headers",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+
+    visibility: ["//frameworks/native/libs/binder:__subpackages__"],
+}
+
 cc_library_headers {
     name: "libbinder_headers_platform_shared",
     export_include_dirs: ["include_cpp"],
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index 34a86f2..d929ec8 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -45,7 +45,9 @@
 
 static const void* kId = "ABBinder";
 static void* kValue = static_cast<void*>(new bool{true});
-void clean(const void* /*id*/, void* /*obj*/, void* /*cookie*/){/* do nothing */};
+void clean(const void* /*id*/, void* /*obj*/, void* /*cookie*/) {
+    /* do nothing */
+}
 
 static void attach(const sp<IBinder>& binder) {
     auto alreadyAttached = binder->attachObject(kId, kValue, nullptr /*cookie*/, clean);
@@ -70,7 +72,7 @@
     LOG_ALWAYS_FATAL_IF(id != kId, "%p %p %p", id, obj, cookie);
 
     delete static_cast<Value*>(obj);
-};
+}
 
 }  // namespace ABpBinderTag
 
@@ -609,6 +611,7 @@
     return recipient->unlinkToDeath(binder->getBinder(), cookie);
 }
 
+#ifdef BINDER_WITH_KERNEL_IPC
 uid_t AIBinder_getCallingUid() {
     return ::android::IPCThreadState::self()->getCallingUid();
 }
@@ -620,6 +623,7 @@
 bool AIBinder_isHandlingTransaction() {
     return ::android::IPCThreadState::self()->getServingStackPointer() != nullptr;
 }
+#endif
 
 void AIBinder_incStrong(AIBinder* binder) {
     if (binder == nullptr) {
@@ -837,9 +841,11 @@
     localBinder->setRequestingSid(requestingSid);
 }
 
+#ifdef BINDER_WITH_KERNEL_IPC
 const char* AIBinder_getCallingSid() {
     return ::android::IPCThreadState::self()->getCallingSid();
 }
+#endif
 
 void AIBinder_setMinSchedulerPolicy(AIBinder* binder, int policy, int priority) {
     binder->asABBinder()->setMinSchedulerPolicy(policy, priority);
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index 57a38dc..ef556d7 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -35,6 +35,21 @@
 }
 
 rust_library {
+    name: "libbinder_rs_on_trusty_mock",
+    crate_name: "binder",
+    srcs: ["src/lib.rs"],
+    cfgs: [
+        "trusty",
+    ],
+    rustlibs: [
+        "libbinder_ndk_sys_on_trusty_mock",
+        "libdowncast_rs",
+        "liblibc",
+    ],
+    vendor: true,
+}
+
+rust_library {
     name: "libbinder_tokio_rs",
     crate_name: "binder_tokio",
     srcs: ["binder_tokio/lib.rs"],
@@ -89,6 +104,26 @@
     visibility: [":__subpackages__"],
 }
 
+rust_library {
+    name: "libbinder_ndk_sys_on_trusty_mock",
+    crate_name: "binder_ndk_sys",
+    srcs: [
+        "sys/lib.rs",
+        ":libbinder_ndk_bindgen_on_trusty_mock",
+    ],
+    cfgs: [
+        "trusty",
+    ],
+    shared_libs: [
+        "libbinder_ndk_on_trusty_mock",
+    ],
+    vendor: true,
+    // Lints are checked separately for libbinder_ndk_sys.
+    // The Trusty mock copy pulls in extra headers that
+    // don't pass the lints for the bindgen output.
+    lints: "none",
+}
+
 rust_bindgen {
     name: "libbinder_ndk_bindgen",
     crate_name: "binder_ndk_bindgen",
@@ -125,6 +160,28 @@
     min_sdk_version: "Tiramisu",
 }
 
+rust_bindgen {
+    name: "libbinder_ndk_bindgen_on_trusty_mock",
+    crate_name: "binder_ndk_bindgen",
+    wrapper_src: "sys/BinderBindings.hpp",
+    source_stem: "bindings",
+    defaults: [
+        "trusty_mock_defaults",
+    ],
+
+    bindgen_flag_files: [
+        // Unfortunately the only way to specify the rust_non_exhaustive enum
+        // style for a type is to make it the default
+        // and then specify constified enums for the enums we don't want
+        // rustified
+        "libbinder_ndk_bindgen_flags.txt",
+    ],
+    shared_libs: [
+        "libbinder_ndk_on_trusty_mock",
+        "libc++",
+    ],
+}
+
 rust_test {
     name: "libbinder_rs-internal_test",
     crate_name: "binder",
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index 535ce01..2e46345 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -18,6 +18,7 @@
         "libbinder_ndk_sys",
         "libbinder_rpc_unstable_bindgen_sys",
         "libbinder_rs",
+        "libcfg_if",
         "libdowncast_rs",
         "libforeign_types",
         "liblibc",
diff --git a/libs/binder/rust/rpcbinder/src/lib.rs b/libs/binder/rust/rpcbinder/src/lib.rs
index 163f000..7e5c9dd 100644
--- a/libs/binder/rust/rpcbinder/src/lib.rs
+++ b/libs/binder/rust/rpcbinder/src/lib.rs
@@ -16,10 +16,10 @@
 
 //! API for RPC Binder services.
 
-#[cfg(not(target_os = "trusty"))]
 mod server;
 mod session;
 
+pub use server::RpcServer;
 #[cfg(not(target_os = "trusty"))]
-pub use server::{RpcServer, RpcServerRef};
+pub use server::RpcServerRef;
 pub use session::{FileDescriptorTransportMode, RpcSession, RpcSessionRef};
diff --git a/libs/binder/rust/rpcbinder/src/server.rs b/libs/binder/rust/rpcbinder/src/server.rs
index 6fda878..d6bdbd8 100644
--- a/libs/binder/rust/rpcbinder/src/server.rs
+++ b/libs/binder/rust/rpcbinder/src/server.rs
@@ -14,160 +14,12 @@
  * limitations under the License.
  */
 
-use crate::session::FileDescriptorTransportMode;
-use binder::{unstable_api::AsNative, SpIBinder};
-use binder_rpc_unstable_bindgen::ARpcServer;
-use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
-use std::ffi::CString;
-use std::io::{Error, ErrorKind};
-use std::os::unix::io::{IntoRawFd, OwnedFd};
-
-foreign_type! {
-    type CType = binder_rpc_unstable_bindgen::ARpcServer;
-    fn drop = binder_rpc_unstable_bindgen::ARpcServer_free;
-
-    /// A type that represents a foreign instance of RpcServer.
-    #[derive(Debug)]
-    pub struct RpcServer;
-    /// A borrowed RpcServer.
-    pub struct RpcServerRef;
-}
-
-/// SAFETY: The opaque handle can be cloned freely.
-unsafe impl Send for RpcServer {}
-/// SAFETY: The underlying C++ RpcServer class is thread-safe.
-unsafe impl Sync for RpcServer {}
-
-impl RpcServer {
-    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
-    /// vsock port. Only connections from the given CID are accepted.
-    ///
-    // Set `cid` to libc::VMADDR_CID_ANY to accept connections from any client.
-    // Set `cid` to libc::VMADDR_CID_LOCAL to only bind to the local vsock interface.
-    pub fn new_vsock(mut service: SpIBinder, cid: u32, port: u32) -> Result<RpcServer, Error> {
-        let service = service.as_native_mut();
-
-        // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
-        // Plus the binder objects are threadsafe.
-        unsafe {
-            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newVsock(
-                service, cid, port,
-            ))
-        }
-    }
-
-    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
-    /// socket file descriptor. The socket should be bound to an address before calling this
-    /// function.
-    pub fn new_bound_socket(
-        mut service: SpIBinder,
-        socket_fd: OwnedFd,
-    ) -> Result<RpcServer, Error> {
-        let service = service.as_native_mut();
-
-        // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
-        // Plus the binder objects are threadsafe.
-        // The server takes ownership of the socket FD.
-        unsafe {
-            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newBoundSocket(
-                service,
-                socket_fd.into_raw_fd(),
-            ))
-        }
-    }
-
-    /// Creates a binder RPC server that bootstraps sessions using an existing Unix domain socket
-    /// pair, with a given root IBinder object. Callers should create a pair of SOCK_STREAM Unix
-    /// domain sockets, pass one to the server and the other to the client. Multiple client session
-    /// can be created from the client end of the pair.
-    pub fn new_unix_domain_bootstrap(
-        mut service: SpIBinder,
-        bootstrap_fd: OwnedFd,
-    ) -> Result<RpcServer, Error> {
-        let service = service.as_native_mut();
-
-        // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
-        // Plus the binder objects are threadsafe.
-        // The server takes ownership of the bootstrap FD.
-        unsafe {
-            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newUnixDomainBootstrap(
-                service,
-                bootstrap_fd.into_raw_fd(),
-            ))
-        }
-    }
-
-    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
-    /// IP address and port.
-    pub fn new_inet(mut service: SpIBinder, address: &str, port: u32) -> Result<RpcServer, Error> {
-        let address = match CString::new(address) {
-            Ok(s) => s,
-            Err(e) => {
-                log::error!("Cannot convert {} to CString. Error: {:?}", address, e);
-                return Err(Error::from(ErrorKind::InvalidInput));
-            }
-        };
-        let service = service.as_native_mut();
-
-        // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
-        // Plus the binder objects are threadsafe.
-        unsafe {
-            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInet(
-                service,
-                address.as_ptr(),
-                port,
-            ))
-        }
-    }
-
-    unsafe fn checked_from_ptr(ptr: *mut ARpcServer) -> Result<RpcServer, Error> {
-        if ptr.is_null() {
-            return Err(Error::new(ErrorKind::Other, "Failed to start server"));
-        }
-        // SAFETY: Our caller must pass us a valid or null pointer, and we've checked that it's not
-        // null.
-        Ok(unsafe { RpcServer::from_ptr(ptr) })
-    }
-}
-
-impl RpcServerRef {
-    /// Sets the list of file descriptor transport modes supported by this server.
-    pub fn set_supported_file_descriptor_transport_modes(
-        &self,
-        modes: &[FileDescriptorTransportMode],
-    ) {
-        // SAFETY: Does not keep the pointer after returning does, nor does it
-        // read past its boundary. Only passes the 'self' pointer as an opaque handle.
-        unsafe {
-            binder_rpc_unstable_bindgen::ARpcServer_setSupportedFileDescriptorTransportModes(
-                self.as_ptr(),
-                modes.as_ptr(),
-                modes.len(),
-            )
-        }
-    }
-
-    /// Starts a new background thread and calls join(). Returns immediately.
-    pub fn start(&self) {
-        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
-        unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
-    }
-
-    /// Joins the RpcServer thread. The call blocks until the server terminates.
-    /// This must be called from exactly one thread.
-    pub fn join(&self) {
-        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
-        unsafe { binder_rpc_unstable_bindgen::ARpcServer_join(self.as_ptr()) };
-    }
-
-    /// Shuts down the running RpcServer. Can be called multiple times and from
-    /// multiple threads. Called automatically during drop().
-    pub fn shutdown(&self) -> Result<(), Error> {
-        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
-        if unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) } {
-            Ok(())
-        } else {
-            Err(Error::from(ErrorKind::UnexpectedEof))
-        }
+cfg_if::cfg_if! {
+    if #[cfg(target_os = "trusty")] {
+        mod trusty;
+        pub use trusty::*;
+    } else {
+        mod android;
+        pub use android::*;
     }
 }
diff --git a/libs/binder/rust/rpcbinder/src/server/android.rs b/libs/binder/rust/rpcbinder/src/server/android.rs
new file mode 100644
index 0000000..ad0365b
--- /dev/null
+++ b/libs/binder/rust/rpcbinder/src/server/android.rs
@@ -0,0 +1,173 @@
+/*
+ * 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::session::FileDescriptorTransportMode;
+use binder::{unstable_api::AsNative, SpIBinder};
+use binder_rpc_unstable_bindgen::ARpcServer;
+use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
+use std::ffi::CString;
+use std::io::{Error, ErrorKind};
+use std::os::unix::io::{IntoRawFd, OwnedFd};
+
+foreign_type! {
+    type CType = binder_rpc_unstable_bindgen::ARpcServer;
+    fn drop = binder_rpc_unstable_bindgen::ARpcServer_free;
+
+    /// A type that represents a foreign instance of RpcServer.
+    #[derive(Debug)]
+    pub struct RpcServer;
+    /// A borrowed RpcServer.
+    pub struct RpcServerRef;
+}
+
+/// SAFETY: The opaque handle can be cloned freely.
+unsafe impl Send for RpcServer {}
+/// SAFETY: The underlying C++ RpcServer class is thread-safe.
+unsafe impl Sync for RpcServer {}
+
+impl RpcServer {
+    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+    /// vsock port. Only connections from the given CID are accepted.
+    ///
+    // Set `cid` to libc::VMADDR_CID_ANY to accept connections from any client.
+    // Set `cid` to libc::VMADDR_CID_LOCAL to only bind to the local vsock interface.
+    pub fn new_vsock(mut service: SpIBinder, cid: u32, port: u32) -> Result<RpcServer, Error> {
+        let service = service.as_native_mut();
+
+        // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+        // Plus the binder objects are threadsafe.
+        unsafe {
+            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newVsock(
+                service, cid, port,
+            ))
+        }
+    }
+
+    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+    /// socket file descriptor. The socket should be bound to an address before calling this
+    /// function.
+    pub fn new_bound_socket(
+        mut service: SpIBinder,
+        socket_fd: OwnedFd,
+    ) -> Result<RpcServer, Error> {
+        let service = service.as_native_mut();
+
+        // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+        // Plus the binder objects are threadsafe.
+        // The server takes ownership of the socket FD.
+        unsafe {
+            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newBoundSocket(
+                service,
+                socket_fd.into_raw_fd(),
+            ))
+        }
+    }
+
+    /// Creates a binder RPC server that bootstraps sessions using an existing Unix domain socket
+    /// pair, with a given root IBinder object. Callers should create a pair of SOCK_STREAM Unix
+    /// domain sockets, pass one to the server and the other to the client. Multiple client session
+    /// can be created from the client end of the pair.
+    pub fn new_unix_domain_bootstrap(
+        mut service: SpIBinder,
+        bootstrap_fd: OwnedFd,
+    ) -> Result<RpcServer, Error> {
+        let service = service.as_native_mut();
+
+        // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+        // Plus the binder objects are threadsafe.
+        // The server takes ownership of the bootstrap FD.
+        unsafe {
+            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newUnixDomainBootstrap(
+                service,
+                bootstrap_fd.into_raw_fd(),
+            ))
+        }
+    }
+
+    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+    /// IP address and port.
+    pub fn new_inet(mut service: SpIBinder, address: &str, port: u32) -> Result<RpcServer, Error> {
+        let address = match CString::new(address) {
+            Ok(s) => s,
+            Err(e) => {
+                log::error!("Cannot convert {} to CString. Error: {:?}", address, e);
+                return Err(Error::from(ErrorKind::InvalidInput));
+            }
+        };
+        let service = service.as_native_mut();
+
+        // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+        // Plus the binder objects are threadsafe.
+        unsafe {
+            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInet(
+                service,
+                address.as_ptr(),
+                port,
+            ))
+        }
+    }
+
+    unsafe fn checked_from_ptr(ptr: *mut ARpcServer) -> Result<RpcServer, Error> {
+        if ptr.is_null() {
+            return Err(Error::new(ErrorKind::Other, "Failed to start server"));
+        }
+        // SAFETY: Our caller must pass us a valid or null pointer, and we've checked that it's not
+        // null.
+        Ok(unsafe { RpcServer::from_ptr(ptr) })
+    }
+}
+
+impl RpcServerRef {
+    /// Sets the list of file descriptor transport modes supported by this server.
+    pub fn set_supported_file_descriptor_transport_modes(
+        &self,
+        modes: &[FileDescriptorTransportMode],
+    ) {
+        // SAFETY: Does not keep the pointer after returning does, nor does it
+        // read past its boundary. Only passes the 'self' pointer as an opaque handle.
+        unsafe {
+            binder_rpc_unstable_bindgen::ARpcServer_setSupportedFileDescriptorTransportModes(
+                self.as_ptr(),
+                modes.as_ptr(),
+                modes.len(),
+            )
+        }
+    }
+
+    /// Starts a new background thread and calls join(). Returns immediately.
+    pub fn start(&self) {
+        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+        unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
+    }
+
+    /// Joins the RpcServer thread. The call blocks until the server terminates.
+    /// This must be called from exactly one thread.
+    pub fn join(&self) {
+        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+        unsafe { binder_rpc_unstable_bindgen::ARpcServer_join(self.as_ptr()) };
+    }
+
+    /// Shuts down the running RpcServer. Can be called multiple times and from
+    /// multiple threads. Called automatically during drop().
+    pub fn shutdown(&self) -> Result<(), Error> {
+        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+        if unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) } {
+            Ok(())
+        } else {
+            Err(Error::from(ErrorKind::UnexpectedEof))
+        }
+    }
+}
diff --git a/libs/binder/rust/rpcbinder/src/server/trusty.rs b/libs/binder/rust/rpcbinder/src/server/trusty.rs
new file mode 100644
index 0000000..fe45dec
--- /dev/null
+++ b/libs/binder/rust/rpcbinder/src/server/trusty.rs
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+use binder::{unstable_api::AsNative, SpIBinder};
+use libc::size_t;
+use std::ffi::{c_char, c_void};
+use std::ptr;
+use tipc::{ConnectResult, Handle, MessageResult, PortCfg, TipcError, UnbufferedService, Uuid};
+
+pub trait PerSessionCallback: Fn(Uuid) -> Option<SpIBinder> + Send + Sync + 'static {}
+impl<T> PerSessionCallback for T where T: Fn(Uuid) -> Option<SpIBinder> + Send + Sync + 'static {}
+
+pub struct RpcServer {
+    inner: *mut binder_rpc_server_bindgen::ARpcServerTrusty,
+}
+
+/// SAFETY: The opaque handle points to a heap allocation
+/// that should be process-wide and not tied to the current thread.
+unsafe impl Send for RpcServer {}
+/// SAFETY: The underlying C++ RpcServer class is thread-safe.
+unsafe impl Sync for RpcServer {}
+
+impl Drop for RpcServer {
+    fn drop(&mut self) {
+        // SAFETY: `ARpcServerTrusty_delete` is the correct destructor to call
+        // on pointers returned by `ARpcServerTrusty_new`.
+        unsafe {
+            binder_rpc_server_bindgen::ARpcServerTrusty_delete(self.inner);
+        }
+    }
+}
+
+impl RpcServer {
+    /// Allocates a new RpcServer object.
+    pub fn new(service: SpIBinder) -> RpcServer {
+        Self::new_per_session(move |_uuid| Some(service.clone()))
+    }
+
+    /// Allocates a new per-session RpcServer object.
+    ///
+    /// Per-session objects take a closure that gets called once
+    /// for every new connection. The closure gets the UUID of
+    /// the peer and can accept or reject that connection.
+    pub fn new_per_session<F: PerSessionCallback>(f: F) -> RpcServer {
+        // SAFETY: Takes ownership of the returned handle, which has correct refcount.
+        let inner = unsafe {
+            binder_rpc_server_bindgen::ARpcServerTrusty_newPerSession(
+                Some(per_session_callback_wrapper::<F>),
+                Box::into_raw(Box::new(f)).cast(),
+                Some(per_session_callback_deleter::<F>),
+            )
+        };
+        RpcServer { inner }
+    }
+}
+
+unsafe extern "C" fn per_session_callback_wrapper<F: PerSessionCallback>(
+    uuid_ptr: *const c_void,
+    len: size_t,
+    cb_ptr: *mut c_char,
+) -> *mut binder_rpc_server_bindgen::AIBinder {
+    // SAFETY: This callback should only get called while the RpcServer is alive.
+    let cb = unsafe { &mut *cb_ptr.cast::<F>() };
+
+    if len != std::mem::size_of::<Uuid>() {
+        return ptr::null_mut();
+    }
+
+    // SAFETY: On the previous lines we check that we got exactly the right amount of bytes.
+    let uuid = unsafe {
+        let mut uuid = std::mem::MaybeUninit::<Uuid>::uninit();
+        uuid.as_mut_ptr().copy_from(uuid_ptr.cast(), 1);
+        uuid.assume_init()
+    };
+
+    cb(uuid).map_or_else(ptr::null_mut, |b| {
+        // Prevent AIBinder_decStrong from being called before AIBinder_toPlatformBinder.
+        // The per-session callback in C++ is supposed to call AIBinder_decStrong on the
+        // pointer we return here.
+        std::mem::ManuallyDrop::new(b).as_native_mut().cast()
+    })
+}
+
+unsafe extern "C" fn per_session_callback_deleter<F: PerSessionCallback>(cb: *mut c_char) {
+    // SAFETY: shared_ptr calls this to delete the pointer we gave it.
+    // It should only get called once the last shared reference goes away.
+    unsafe {
+        drop(Box::<F>::from_raw(cb.cast()));
+    }
+}
+
+pub struct RpcServerConnection {
+    ctx: *mut c_void,
+}
+
+impl Drop for RpcServerConnection {
+    fn drop(&mut self) {
+        // We do not need to close handle_fd since we do not own it.
+        unsafe {
+            binder_rpc_server_bindgen::ARpcServerTrusty_handleChannelCleanup(self.ctx);
+        }
+    }
+}
+
+impl UnbufferedService for RpcServer {
+    type Connection = RpcServerConnection;
+
+    fn on_connect(
+        &self,
+        _port: &PortCfg,
+        handle: &Handle,
+        peer: &Uuid,
+    ) -> tipc::Result<ConnectResult<Self::Connection>> {
+        let mut conn = RpcServerConnection { ctx: std::ptr::null_mut() };
+        let rc = unsafe {
+            binder_rpc_server_bindgen::ARpcServerTrusty_handleConnect(
+                self.inner,
+                handle.as_raw_fd(),
+                peer.as_ptr().cast(),
+                &mut conn.ctx,
+            )
+        };
+        if rc < 0 {
+            Err(TipcError::from_uapi(rc.into()))
+        } else {
+            Ok(ConnectResult::Accept(conn))
+        }
+    }
+
+    fn on_message(
+        &self,
+        conn: &Self::Connection,
+        _handle: &Handle,
+        buffer: &mut [u8],
+    ) -> tipc::Result<MessageResult> {
+        assert!(buffer.is_empty());
+        let rc = unsafe { binder_rpc_server_bindgen::ARpcServerTrusty_handleMessage(conn.ctx) };
+        if rc < 0 {
+            Err(TipcError::from_uapi(rc.into()))
+        } else {
+            Ok(MessageResult::MaintainConnection)
+        }
+    }
+
+    fn on_disconnect(&self, conn: &Self::Connection) {
+        unsafe { binder_rpc_server_bindgen::ARpcServerTrusty_handleDisconnect(conn.ctx) };
+    }
+}
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 16049f2..0f9c58c 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -100,7 +100,9 @@
 mod native;
 mod parcel;
 mod proxy;
-#[cfg(not(target_os = "trusty"))]
+#[cfg(not(trusty))]
+mod service;
+#[cfg(not(trusty))]
 mod state;
 
 use binder_ndk_sys as sys;
@@ -108,16 +110,15 @@
 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(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"))]
+#[cfg(not(trusty))]
 pub use state::{ProcessState, ThreadState};
 
 /// Binder result containing a [`Status`] on error.
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index 8ae010e..c87cc94 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -23,12 +23,11 @@
 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.
 ///
@@ -328,7 +327,7 @@
     /// contains a `T` pointer in its user data. fd should be a non-owned file
     /// descriptor, and args must be an array of null-terminated string
     /// pointers with length num_args.
-    #[cfg(not(target_os = "trusty"))]
+    #[cfg(not(trusty))]
     unsafe extern "C" fn on_dump(
         binder: *mut sys::AIBinder,
         fd: i32,
@@ -375,7 +374,7 @@
     }
 
     /// Called to handle the `dump` transaction.
-    #[cfg(target_os = "trusty")]
+    #[cfg(trusty)]
     unsafe extern "C" fn on_dump(
         _binder: *mut sys::AIBinder,
         _fd: i32,
@@ -462,110 +461,6 @@
     }
 }
 
-/// 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 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 7434e9d..340014a 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -29,11 +29,10 @@
 
 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 @@
     }
 }
 
-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 @@
     }
 }
 
-/// 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 0000000..3ca3b54
--- /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/rust/sys/lib.rs b/libs/binder/rust/sys/lib.rs
index c5c847b..5352473 100644
--- a/libs/binder/rust/sys/lib.rs
+++ b/libs/binder/rust/sys/lib.rs
@@ -25,7 +25,9 @@
 }
 
 // Trusty puts the full path to the auto-generated file in BINDGEN_INC_FILE
-// and builds it with warnings-as-errors, so we need to use #[allow(bad_style)]
+// and builds it with warnings-as-errors, so we need to use #[allow(bad_style)].
+// We need to use cfg(target_os) instead of cfg(trusty) here because of
+// the difference between the two build systems, which we cannot mock.
 #[cfg(target_os = "trusty")]
 #[allow(bad_style)]
 mod bindings {
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 2f0987f..35002eb 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -435,6 +435,8 @@
     // Add the Trusty mock library as a fake dependency so it gets built
     required: [
         "libbinder_on_trusty_mock",
+        "libbinder_ndk_on_trusty_mock",
+        "libbinder_rs_on_trusty_mock",
         "binderRpcTestService_on_trusty_mock",
         "binderRpcTest_on_trusty_mock",
     ],
diff --git a/libs/binder/trusty/RpcServerTrusty.cpp b/libs/binder/trusty/RpcServerTrusty.cpp
index 1f857a0..17919c2 100644
--- a/libs/binder/trusty/RpcServerTrusty.cpp
+++ b/libs/binder/trusty/RpcServerTrusty.cpp
@@ -60,7 +60,7 @@
 
 RpcServerTrusty::RpcServerTrusty(std::unique_ptr<RpcTransportCtx> ctx, std::string&& portName,
                                  std::shared_ptr<const PortAcl>&& portAcl, size_t msgMaxSize)
-      : mRpcServer(sp<RpcServer>::make(std::move(ctx))),
+      : mRpcServer(makeRpcServer(std::move(ctx))),
         mPortName(std::move(portName)),
         mPortAcl(std::move(portAcl)) {
     mTipcPort.name = mPortName.c_str();
@@ -68,10 +68,6 @@
     mTipcPort.msg_queue_len = 6; // Three each way
     mTipcPort.priv = this;
 
-    // TODO(b/266741352): follow-up to prevent needing this in the future
-    // Trusty needs to be set to the latest stable version that is in prebuilts there.
-    LOG_ALWAYS_FATAL_IF(!mRpcServer->setProtocolVersion(0));
-
     if (mPortAcl) {
         // Initialize the array of pointers to uuids.
         // The pointers in mUuidPtrs should stay valid across moves of
@@ -101,8 +97,13 @@
 int RpcServerTrusty::handleConnect(const tipc_port* port, handle_t chan, const uuid* peer,
                                    void** ctx_p) {
     auto* server = reinterpret_cast<RpcServerTrusty*>(const_cast<void*>(port->priv));
-    server->mRpcServer->mShutdownTrigger = FdTrigger::make();
-    server->mRpcServer->mConnectingThreads[rpc_this_thread::get_id()] = RpcMaybeThread();
+    return handleConnectInternal(server->mRpcServer.get(), chan, peer, ctx_p);
+}
+
+int RpcServerTrusty::handleConnectInternal(RpcServer* rpcServer, handle_t chan, const uuid* peer,
+                                           void** ctx_p) {
+    rpcServer->mShutdownTrigger = FdTrigger::make();
+    rpcServer->mConnectingThreads[rpc_this_thread::get_id()] = RpcMaybeThread();
 
     int rc = NO_ERROR;
     auto joinFn = [&](sp<RpcSession>&& session, RpcSession::PreJoinSetupResult&& result) {
@@ -138,13 +139,17 @@
     std::array<uint8_t, RpcServer::kRpcAddressSize> addr;
     constexpr size_t addrLen = sizeof(*peer);
     memcpy(addr.data(), peer, addrLen);
-    RpcServer::establishConnection(sp(server->mRpcServer), std::move(transportFd), addr, addrLen,
-                                   joinFn);
+    RpcServer::establishConnection(sp<RpcServer>::fromExisting(rpcServer), std::move(transportFd),
+                                   addr, addrLen, joinFn);
 
     return rc;
 }
 
 int RpcServerTrusty::handleMessage(const tipc_port* /*port*/, handle_t /*chan*/, void* ctx) {
+    return handleMessageInternal(ctx);
+}
+
+int RpcServerTrusty::handleMessageInternal(void* ctx) {
     auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
     LOG_ALWAYS_FATAL_IF(channelContext == nullptr,
                         "bad state: message received on uninitialized channel");
@@ -162,6 +167,10 @@
 }
 
 void RpcServerTrusty::handleDisconnect(const tipc_port* /*port*/, handle_t /*chan*/, void* ctx) {
+    return handleDisconnectInternal(ctx);
+}
+
+void RpcServerTrusty::handleDisconnectInternal(void* ctx) {
     auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
     if (channelContext == nullptr) {
         // Connections marked "incoming" (outgoing from the server's side)
diff --git a/libs/binder/trusty/build-config-usertests b/libs/binder/trusty/build-config-usertests
index d0a1fbc..72e5ff9 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/include/binder/ARpcServerTrusty.h b/libs/binder/trusty/include/binder/ARpcServerTrusty.h
new file mode 100644
index 0000000..c82268b
--- /dev/null
+++ b/libs/binder/trusty/include/binder/ARpcServerTrusty.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <lib/tipc/tipc_srv.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct AIBinder;
+struct ARpcServerTrusty;
+
+struct ARpcServerTrusty* ARpcServerTrusty_newPerSession(struct AIBinder* (*)(const void*, size_t,
+                                                                             char*),
+                                                        char*, void (*)(char*));
+void ARpcServerTrusty_delete(struct ARpcServerTrusty*);
+int ARpcServerTrusty_handleConnect(struct ARpcServerTrusty*, handle_t, const struct uuid*, void**);
+int ARpcServerTrusty_handleMessage(void*);
+void ARpcServerTrusty_handleDisconnect(void*);
+void ARpcServerTrusty_handleChannelCleanup(void*);
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/libs/binder/trusty/include/binder/RpcServerTrusty.h b/libs/binder/trusty/include/binder/RpcServerTrusty.h
index f35d6c2..fe44ea5 100644
--- a/libs/binder/trusty/include/binder/RpcServerTrusty.h
+++ b/libs/binder/trusty/include/binder/RpcServerTrusty.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <binder/ARpcServerTrusty.h>
 #include <binder/IBinder.h>
 #include <binder/RpcServer.h>
 #include <binder/RpcSession.h>
@@ -88,6 +89,28 @@
     explicit RpcServerTrusty(std::unique_ptr<RpcTransportCtx> ctx, std::string&& portName,
                              std::shared_ptr<const PortAcl>&& portAcl, size_t msgMaxSize);
 
+    // Internal helper that creates the RpcServer.
+    // This is used both from here and Rust.
+    static sp<RpcServer> makeRpcServer(std::unique_ptr<RpcTransportCtx> ctx) {
+        auto rpcServer = sp<RpcServer>::make(std::move(ctx));
+
+        // TODO(b/266741352): follow-up to prevent needing this in the future
+        // Trusty needs to be set to the latest stable version that is in prebuilts there.
+        LOG_ALWAYS_FATAL_IF(!rpcServer->setProtocolVersion(0));
+
+        return rpcServer;
+    }
+
+    friend struct ::ARpcServerTrusty;
+    friend ::ARpcServerTrusty* ::ARpcServerTrusty_newPerSession(::AIBinder* (*)(const void*, size_t,
+                                                                                char*),
+                                                                char*, void (*)(char*));
+    friend void ::ARpcServerTrusty_delete(::ARpcServerTrusty*);
+    friend int ::ARpcServerTrusty_handleConnect(::ARpcServerTrusty*, handle_t, const uuid*, void**);
+    friend int ::ARpcServerTrusty_handleMessage(void*);
+    friend void ::ARpcServerTrusty_handleDisconnect(void*);
+    friend void ::ARpcServerTrusty_handleChannelCleanup(void*);
+
     // The Rpc-specific context maintained for every open TIPC channel.
     struct ChannelContext {
         sp<RpcSession> session;
@@ -99,6 +122,11 @@
     static void handleDisconnect(const tipc_port* port, handle_t chan, void* ctx);
     static void handleChannelCleanup(void* ctx);
 
+    static int handleConnectInternal(RpcServer* rpcServer, handle_t chan, const uuid* peer,
+                                     void** ctx_p);
+    static int handleMessageInternal(void* ctx);
+    static void handleDisconnectInternal(void* ctx);
+
     static constexpr tipc_srv_ops kTipcOps = {
             .on_connect = &handleConnect,
             .on_message = &handleMessage,
diff --git a/libs/binder/trusty/ndk/Android.bp b/libs/binder/trusty/ndk/Android.bp
new file mode 100644
index 0000000..af9874a
--- /dev/null
+++ b/libs/binder/trusty/ndk/Android.bp
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library_headers {
+    name: "libbinder_trusty_ndk_headers",
+    export_include_dirs: ["include"],
+    host_supported: true,
+    vendor_available: true,
+}
diff --git a/libs/binder/trusty/ndk/include/sys/cdefs.h b/libs/binder/trusty/ndk/include/sys/cdefs.h
index eabfe60..7528f2b 100644
--- a/libs/binder/trusty/ndk/include/sys/cdefs.h
+++ b/libs/binder/trusty/ndk/include/sys/cdefs.h
@@ -15,11 +15,16 @@
  */
 #pragma once
 
+#if __has_include(<lk/compiler.h>)
 #include <lk/compiler.h>
 
 /* Alias the bionic macros to the ones from lk/compiler.h */
 #define __BEGIN_DECLS __BEGIN_CDECLS
 #define __END_DECLS __END_CDECLS
 
+#else // __has_include(<lk/compiler.h>)
+#include_next <sys/cdefs.h>
+#endif
+
 #define __INTRODUCED_IN(x) /* nothing on Trusty */
 #define __INTRODUCED_IN_LLNDK(x) /* nothing on Trusty */
diff --git a/libs/binder/trusty/rust/binder_ndk_sys/rules.mk b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
index 672d9b7..2aaa061 100644
--- a/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
+++ b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
@@ -29,6 +29,10 @@
 	$(LIBBINDER_DIR)/trusty/ndk \
 	trusty/user/base/lib/trusty-sys \
 
+MODULE_RUSTFLAGS += \
+	--cfg 'android_vendor' \
+	--cfg 'trusty' \
+
 MODULE_BINDGEN_SRC_HEADER := $(LIBBINDER_DIR)/rust/sys/BinderBindings.hpp
 
 # Add the flags from the flag file
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp
new file mode 100644
index 0000000..451383a
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+#include <android/binder_libbinder.h>
+#include <binder/RpcServer.h>
+#include <binder/RpcServerTrusty.h>
+#include <binder/RpcSession.h>
+#include <binder/RpcTransportTipcTrusty.h>
+
+using android::RpcServer;
+using android::RpcServerTrusty;
+using android::RpcSession;
+using android::RpcTransportCtxFactoryTipcTrusty;
+using android::sp;
+using android::wp;
+
+struct ARpcServerTrusty {
+    sp<RpcServer> mRpcServer;
+
+    ARpcServerTrusty() = delete;
+    ARpcServerTrusty(sp<RpcServer> rpcServer) : mRpcServer(std::move(rpcServer)) {}
+};
+
+ARpcServerTrusty* ARpcServerTrusty_newPerSession(AIBinder* (*cb)(const void*, size_t, char*),
+                                                 char* cbArg, void (*cbArgDeleter)(char*)) {
+    std::shared_ptr<char> cbArgSp(cbArg, cbArgDeleter);
+
+    auto rpcTransportCtxFactory = RpcTransportCtxFactoryTipcTrusty::make();
+    if (rpcTransportCtxFactory == nullptr) {
+        return nullptr;
+    }
+
+    auto ctx = rpcTransportCtxFactory->newServerCtx();
+    if (ctx == nullptr) {
+        return nullptr;
+    }
+
+    auto rpcServer = RpcServerTrusty::makeRpcServer(std::move(ctx));
+    if (rpcServer == nullptr) {
+        return nullptr;
+    }
+
+    rpcServer->setPerSessionRootObject(
+            [cb, cbArgSp](wp<RpcSession> /*session*/, const void* addrPtr, size_t len) {
+                auto* aib = (*cb)(addrPtr, len, cbArgSp.get());
+                auto b = AIBinder_toPlatformBinder(aib);
+
+                // We have a new sp<IBinder> backed by the same binder, so we can
+                // finally release the AIBinder* from the callback
+                AIBinder_decStrong(aib);
+
+                return b;
+            });
+
+    return new (std::nothrow) ARpcServerTrusty(std::move(rpcServer));
+}
+
+void ARpcServerTrusty_delete(ARpcServerTrusty* rstr) {
+    delete rstr;
+}
+
+int ARpcServerTrusty_handleConnect(ARpcServerTrusty* rstr, handle_t chan, const uuid* peer,
+                                   void** ctx_p) {
+    return RpcServerTrusty::handleConnectInternal(rstr->mRpcServer.get(), chan, peer, ctx_p);
+}
+
+int ARpcServerTrusty_handleMessage(void* ctx) {
+    return RpcServerTrusty::handleMessageInternal(ctx);
+}
+
+void ARpcServerTrusty_handleDisconnect(void* ctx) {
+    RpcServerTrusty::handleDisconnectInternal(ctx);
+}
+
+void ARpcServerTrusty_handleChannelCleanup(void* ctx) {
+    RpcServerTrusty::handleChannelCleanup(ctx);
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk
new file mode 100644
index 0000000..6def634
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk
@@ -0,0 +1,29 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := \
+	$(LOCAL_DIR)/ARpcServerTrusty.cpp \
+
+MODULE_LIBRARY_DEPS += \
+	$(LIBBINDER_DIR)/trusty \
+	$(LIBBINDER_DIR)/trusty/ndk \
+	trusty/user/base/lib/libstdc++-trusty \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs b/libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs
new file mode 100644
index 0000000..2e8b3ec
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+//! Generated Rust bindings to binder_rpc_server
+
+#[allow(bad_style)]
+mod sys {
+    include!(env!("BINDGEN_INC_FILE"));
+}
+
+pub use sys::*;
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk b/libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk
new file mode 100644
index 0000000..4ee333f
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk
@@ -0,0 +1,37 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/lib.rs
+
+MODULE_CRATE_NAME := binder_rpc_server_bindgen
+
+MODULE_LIBRARY_DEPS += \
+	$(LOCAL_DIR)/cpp \
+	trusty/user/base/lib/libstdc++-trusty \
+	trusty/user/base/lib/trusty-sys \
+
+MODULE_BINDGEN_SRC_HEADER := \
+	$(LIBBINDER_DIR)/trusty/include/binder/ARpcServerTrusty.h
+
+MODULE_BINDGEN_FLAGS += \
+	--allowlist-type="ARpcServerTrusty" \
+	--allowlist-function="ARpcServerTrusty_.*" \
+
+include make/library.mk
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 0000000..1b0dca0
--- /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 0000000..3c1e784
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/main.rs
@@ -0,0 +1,43 @@
+/*
+ * 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";
+const RUST_SERVICE_PORT: &str = "com.android.trusty.rust.binderRpcTestService.V1";
+
+fn get_service(port: &str) -> Strong<dyn IBinderRpcTest> {
+    let port = CString::try_new(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(SERVICE_PORT);
+    assert_eq!(srv.as_binder().ping_binder(), Ok(()));
+}
+
+#[test]
+fn ping_rust() {
+    let srv = get_service(RUST_SERVICE_PORT);
+    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 0000000..c2ecaa4
--- /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 0000000..192a159
--- /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/rust/binder_rpc_test/service/main.rs b/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
new file mode 100644
index 0000000..b9a86bf
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+use binder::{BinderFeatures, Interface, ParcelFileDescriptor, SpIBinder, Status, Strong};
+use binder_rpc_test_aidl::aidl::IBinderRpcCallback::IBinderRpcCallback;
+use binder_rpc_test_aidl::aidl::IBinderRpcSession::IBinderRpcSession;
+use binder_rpc_test_aidl::aidl::IBinderRpcTest::{BnBinderRpcTest, IBinderRpcTest};
+use rpcbinder::RpcServer;
+use std::rc::Rc;
+use tipc::{service_dispatcher, wrap_service, Manager, PortCfg};
+
+const RUST_SERVICE_PORT: &str = "com.android.trusty.rust.binderRpcTestService.V1";
+
+#[derive(Debug, Default)]
+struct TestService;
+
+impl Interface for TestService {}
+
+impl IBinderRpcTest for TestService {
+    fn sendString(&self, _: &str) -> Result<(), Status> {
+        todo!()
+    }
+    fn doubleString(&self, _: &str) -> Result<String, Status> {
+        todo!()
+    }
+    fn getClientPort(&self) -> Result<i32, Status> {
+        todo!()
+    }
+    fn countBinders(&self) -> Result<Vec<i32>, Status> {
+        todo!()
+    }
+    fn getNullBinder(&self) -> Result<SpIBinder, Status> {
+        todo!()
+    }
+    fn pingMe(&self, _: &SpIBinder) -> Result<i32, Status> {
+        todo!()
+    }
+    fn repeatBinder(&self, _: Option<&SpIBinder>) -> Result<Option<SpIBinder>, Status> {
+        todo!()
+    }
+    fn holdBinder(&self, _: Option<&SpIBinder>) -> Result<(), Status> {
+        todo!()
+    }
+    fn getHeldBinder(&self) -> Result<Option<SpIBinder>, Status> {
+        todo!()
+    }
+    fn nestMe(&self, _: &Strong<(dyn IBinderRpcTest + 'static)>, _: i32) -> Result<(), Status> {
+        todo!()
+    }
+    fn alwaysGiveMeTheSameBinder(&self) -> Result<SpIBinder, Status> {
+        todo!()
+    }
+    fn openSession(&self, _: &str) -> Result<Strong<(dyn IBinderRpcSession + 'static)>, Status> {
+        todo!()
+    }
+    fn getNumOpenSessions(&self) -> Result<i32, Status> {
+        todo!()
+    }
+    fn lock(&self) -> Result<(), Status> {
+        todo!()
+    }
+    fn unlockInMsAsync(&self, _: i32) -> Result<(), Status> {
+        todo!()
+    }
+    fn lockUnlock(&self) -> Result<(), Status> {
+        todo!()
+    }
+    fn sleepMs(&self, _: i32) -> Result<(), Status> {
+        todo!()
+    }
+    fn sleepMsAsync(&self, _: i32) -> Result<(), Status> {
+        todo!()
+    }
+    fn doCallback(
+        &self,
+        _: &Strong<(dyn IBinderRpcCallback + 'static)>,
+        _: bool,
+        _: bool,
+        _: &str,
+    ) -> Result<(), Status> {
+        todo!()
+    }
+    fn doCallbackAsync(
+        &self,
+        _: &Strong<(dyn IBinderRpcCallback + 'static)>,
+        _: bool,
+        _: bool,
+        _: &str,
+    ) -> Result<(), Status> {
+        todo!()
+    }
+    fn die(&self, _: bool) -> Result<(), Status> {
+        todo!()
+    }
+    fn scheduleShutdown(&self) -> Result<(), Status> {
+        todo!()
+    }
+    fn useKernelBinderCallingId(&self) -> Result<(), Status> {
+        todo!()
+    }
+    fn echoAsFile(&self, _: &str) -> Result<ParcelFileDescriptor, Status> {
+        todo!()
+    }
+    fn concatFiles(&self, _: &[ParcelFileDescriptor]) -> Result<ParcelFileDescriptor, Status> {
+        todo!()
+    }
+    fn blockingSendFdOneway(&self, _: &ParcelFileDescriptor) -> Result<(), Status> {
+        todo!()
+    }
+    fn blockingRecvFd(&self) -> Result<ParcelFileDescriptor, Status> {
+        todo!()
+    }
+    fn blockingSendIntOneway(&self, _: i32) -> Result<(), Status> {
+        todo!()
+    }
+    fn blockingRecvInt(&self) -> Result<i32, Status> {
+        todo!()
+    }
+}
+
+wrap_service!(TestRpcServer(RpcServer: UnbufferedService));
+
+service_dispatcher! {
+    enum TestDispatcher {
+        TestRpcServer,
+    }
+}
+
+fn main() {
+    let mut dispatcher = TestDispatcher::<1>::new().expect("Could not create test dispatcher");
+
+    let service = BnBinderRpcTest::new_binder(TestService::default(), BinderFeatures::default());
+    let rpc_server =
+        TestRpcServer::new(RpcServer::new_per_session(move |_uuid| Some(service.as_binder())));
+
+    let cfg = PortCfg::new(RUST_SERVICE_PORT)
+        .expect("Could not create port config")
+        .allow_ta_connect()
+        .allow_ns_connect();
+    dispatcher.add_service(Rc::new(rpc_server), cfg).expect("Could not add service to dispatcher");
+
+    Manager::<_, _, 1, 4>::new_with_dispatcher(dispatcher, [])
+        .expect("Could not create service manager")
+        .run_event_loop()
+        .expect("Test event loop failed");
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/manifest.json b/libs/binder/trusty/rust/binder_rpc_test/service/manifest.json
new file mode 100644
index 0000000..121ba11
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/manifest.json
@@ -0,0 +1,10 @@
+{
+    "uuid": "4741fc65-8b65-4893-ba55-b182c003c8b7",
+    "app_name": "rust_binder_rpc_test_service",
+    "min_heap": 16384,
+    "min_stack": 16384,
+    "mgmt_flags": {
+        "non_critical_app": true,
+        "restart_on_exit": true
+    }
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/service/rules.mk
new file mode 100644
index 0000000..1ddc382
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/rules.mk
@@ -0,0 +1,33 @@
+# 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_service
+
+MODULE_LIBRARY_DEPS += \
+	$(LIBBINDER_DIR)/trusty/rust \
+	$(LIBBINDER_DIR)/trusty/rust/rpcbinder \
+	$(LOCAL_DIR)/../aidl \
+	trusty/user/base/lib/tipc/rust \
+
+MANIFEST := $(LOCAL_DIR)/manifest.json
+
+include make/trusted_app.mk
diff --git a/libs/binder/trusty/rust/rpcbinder/rules.mk b/libs/binder/trusty/rust/rpcbinder/rules.mk
index 76f3b94..97f5c03 100644
--- a/libs/binder/trusty/rust/rpcbinder/rules.mk
+++ b/libs/binder/trusty/rust/rpcbinder/rules.mk
@@ -28,6 +28,8 @@
 	$(LIBBINDER_DIR)/trusty/rust \
 	$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
 	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
+	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_server_bindgen \
+	external/rust/crates/cfg-if \
 	external/rust/crates/foreign-types \
 	trusty/user/base/lib/tipc/rust \
 	trusty/user/base/lib/trusty-sys \
diff --git a/libs/binder/trusty/rust/rules.mk b/libs/binder/trusty/rust/rules.mk
index d343f14..c5e671a 100644
--- a/libs/binder/trusty/rust/rules.mk
+++ b/libs/binder/trusty/rust/rules.mk
@@ -32,6 +32,7 @@
 
 MODULE_RUSTFLAGS += \
 	--cfg 'android_vendor' \
+	--cfg 'trusty' \
 
 # Trusty does not have `ProcessState`, so there are a few
 # doc links in `IBinder` that are still broken.
diff --git a/libs/binder/trusty/usertests-inc.mk b/libs/binder/trusty/usertests-inc.mk
index 1300121..833d209 100644
--- a/libs/binder/trusty/usertests-inc.mk
+++ b/libs/binder/trusty/usertests-inc.mk
@@ -16,4 +16,8 @@
 TRUSTY_USER_TESTS += \
 	frameworks/native/libs/binder/trusty/binderRpcTest \
 	frameworks/native/libs/binder/trusty/binderRpcTest/service \
+	frameworks/native/libs/binder/trusty/rust/binder_rpc_test/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 3c8c41f..196161b 100644
--- a/libs/bufferqueueconverter/Android.bp
+++ b/libs/bufferqueueconverter/Android.bp
@@ -17,9 +17,6 @@
 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 f300da5..8dabc2c 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -38,10 +38,7 @@
     },
 
     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 d1b2c5f..6c45746 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -305,10 +305,6 @@
 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 3d29529..4e86ed6 100644
--- a/libs/gui/rust/aidl_types/src/lib.rs
+++ b/libs/gui/rust/aidl_types/src/lib.rs
@@ -20,9 +20,11 @@
     StatusCode,
 };
 
+#[allow(dead_code)]
 macro_rules! stub_unstructured_parcelable {
     ($name:ident) => {
         /// Unimplemented stub parcelable.
+        #[allow(dead_code)]
         #[derive(Debug, Default)]
         pub struct $name(Option<()>);
 
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index e161c2a..5c4b889 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -135,7 +135,13 @@
   description: "Enable prediction pruning based on jerk thresholds."
   bug: "266747654"
   is_fixed_read_only: true
+}
 
+flag {
+  name: "device_associations"
+  namespace: "input"
+  description: "Binds InputDevice name and InputDevice description against display unique id."
+  bug: "324075859"
 }
 
 flag {
@@ -144,3 +150,10 @@
   description: "Allow multiple input devices to be active in the same window simultaneously"
   bug: "330752824"
 }
+
+flag {
+  name: "hide_pointer_indicators_for_secure_windows"
+  namespace: "input"
+  description: "Hide touch and pointer indicators if a secure window is present on display"
+  bug: "325252005"
+}
diff --git a/libs/nativewindow/rust/Android.bp b/libs/nativewindow/rust/Android.bp
index a3df482..97740db 100644
--- a/libs/nativewindow/rust/Android.bp
+++ b/libs/nativewindow/rust/Android.bp
@@ -54,6 +54,10 @@
     },
     min_sdk_version: "VanillaIceCream",
     vendor_available: true,
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
 }
 
 rust_library {
@@ -78,6 +82,10 @@
     },
     min_sdk_version: "VanillaIceCream",
     vendor_available: true,
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
 }
 
 rust_test {
@@ -116,6 +124,10 @@
     },
     min_sdk_version: "VanillaIceCream",
     vendor_available: true,
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
 }
 
 rust_test {
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index 22ad834..dc3f51f 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -16,7 +16,8 @@
 
 extern crate nativewindow_bindgen as ffi;
 
-pub mod surface;
+mod surface;
+pub use surface::Surface;
 
 pub use ffi::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
 
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 9cb298a..12230f9 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -105,9 +105,6 @@
 cc_library_shared {
     name: "libui",
     vendor_available: true,
-    vndk: {
-        enabled: true,
-    },
     double_loadable: true,
 
     cflags: [
@@ -280,13 +277,3 @@
     "tests",
     "tools",
 ]
-
-filegroup {
-    name: "libui_host_common",
-    srcs: [
-        "Rect.cpp",
-        "Region.cpp",
-        "PixelFormat.cpp",
-        "Transform.cpp",
-    ],
-}
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 6ccd9e7..417c1f3 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -20,6 +20,9 @@
 
 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 @@
     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 4d3b768..0d4cbb0 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 6c31442..a9bdbec 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 @@
 
 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/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 293ad66..fe4d0cd 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -1,10 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsWindowManagerDeviceWindow",
+      "name": "CtsWindowManagerDeviceInput",
       "options": [
         {
-          "include-filter": "android.server.wm.window.WindowInputTests"
+          "include-filter": "android.server.wm.input.WindowInputTests"
         }
       ]
     },
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
index 2f6b8fc..cc0592e 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 @@
   * 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 @@
     /** 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/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 76c492e..d1930f1 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2455,7 +2455,7 @@
         if (newTouchedWindowHandle == nullptr) {
             ALOGD("No new touched window at (%.1f, %.1f) in display %" PRId32, x, y, displayId);
             // Try to assign the pointer to the first foreground window we find, if there is one.
-            newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle();
+            newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
         }
 
         // Verify targeted injection.
@@ -2623,7 +2623,7 @@
             const auto [x, y] = resolveTouchedPosition(entry);
             const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0);
             sp<WindowInfoHandle> oldTouchedWindowHandle =
-                    tempTouchState.getFirstForegroundWindowHandle();
+                    tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
             LOG_ALWAYS_FATAL_IF(oldTouchedWindowHandle == nullptr);
             sp<WindowInfoHandle> newTouchedWindowHandle =
                     findTouchedWindowAtLocked(displayId, x, y, isStylus);
@@ -2741,7 +2741,7 @@
     // has a different UID, then we will not reveal coordinate information to this window.
     if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
         sp<WindowInfoHandle> foregroundWindowHandle =
-                tempTouchState.getFirstForegroundWindowHandle();
+                tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
         if (foregroundWindowHandle) {
             const auto foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
             for (InputTarget& target : targets) {
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index c2d2b7b..02bc368 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -338,9 +338,8 @@
         // 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/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 0caa5e1..15eef20 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -180,9 +180,11 @@
     clearWindowsWithoutPointers();
 }
 
-sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
-    for (size_t i = 0; i < windows.size(); i++) {
-        const TouchedWindow& window = windows[i];
+sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle(DeviceId deviceId) const {
+    for (const auto& window : windows) {
+        if (!window.hasTouchingPointers(deviceId)) {
+            continue;
+        }
         if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
             return window.windowHandle;
         }
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 559a3fd..e63edc3 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -64,7 +64,7 @@
     // set to false.
     void cancelPointersForNonPilferingWindows();
 
-    sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
+    sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle(DeviceId deviceId) const;
     bool isSlippery() const;
     sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
     const TouchedWindow& getTouchedWindow(
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
index 9c39743..91ebe9b 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -149,6 +149,8 @@
 
 // --- PerfettoBackend ---
 
+bool PerfettoBackend::sUseInProcessBackendForTest{false};
+
 std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{};
 
 std::atomic<int32_t> PerfettoBackend::sNextInstanceId{1};
@@ -159,7 +161,8 @@
     // 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 @@
                                        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 @@
                                     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 @@
                                           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 e945066..fdfe495 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -51,6 +51,8 @@
 public:
     using GetPackageUid = std::function<gui::Uid(std::string)>;
 
+    static bool sUseInProcessBackendForTest;
+
     explicit PerfettoBackend(GetPackageUid);
     ~PerfettoBackend() override = default;
 
@@ -61,6 +63,7 @@
 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 77d09cb..3c3c15a 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -57,6 +57,7 @@
                                                 const TracedEventMetadata& metadata) {
     std::scoped_lock lock(mLock);
     mQueue.emplace_back(event, metadata);
+    setIdleStatus(false);
     mThreadWakeCondition.notify_all();
 }
 
@@ -65,6 +66,7 @@
                                              const TracedEventMetadata& metadata) {
     std::scoped_lock lock(mLock);
     mQueue.emplace_back(event, metadata);
+    setIdleStatus(false);
     mThreadWakeCondition.notify_all();
 }
 
@@ -73,6 +75,7 @@
                                                    const TracedEventMetadata& metadata) {
     std::scoped_lock lock(mLock);
     mQueue.emplace_back(dispatchArgs, metadata);
+    setIdleStatus(false);
     mThreadWakeCondition.notify_all();
 }
 
@@ -84,10 +87,15 @@
         std::unique_lock lock(mLock);
         base::ScopedLockAssertion assumeLocked(mLock);
 
+        if (mQueue.empty()) {
+            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;
         }
 
@@ -109,6 +117,36 @@
     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 650a87e..52a84c4 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
@@ -42,6 +42,9 @@
     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 @@
                       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/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 79c8a4b..77e672c 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -111,9 +111,13 @@
     // Used to determine which DisplayViewport should be tied to which InputDevice.
     std::unordered_map<std::string, uint8_t> portAssociations;
 
-    // The associations between input device physical port locations and display unique ids.
+    // The associations between input device ports and display unique ids.
     // Used to determine which DisplayViewport should be tied to which InputDevice.
-    std::unordered_map<std::string, std::string> uniqueIdAssociations;
+    std::unordered_map<std::string, std::string> uniqueIdAssociationsByPort;
+
+    // The associations between input device descriptor and display unique ids.
+    // Used to determine which DisplayViewport should be tied to which InputDevice.
+    std::unordered_map<std::string, std::string> uniqueIdAssociationsByDescriptor;
 
     // The associations between input device ports device types.
     // This is used to determine which device type and source should be tied to which InputDevice.
diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h
index 8ffbc11..1bd5595 100644
--- a/services/inputflinger/include/NotifyArgsBuilders.h
+++ b/services/inputflinger/include/NotifyArgsBuilders.h
@@ -30,8 +30,11 @@
 
 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 @@
         return *this;
     }
 
-    NotifyMotionArgs build() {
+    NotifyMotionArgs build() const {
         std::vector<PointerProperties> pointerProperties;
         std::vector<PointerCoords> pointerCoords;
         for (const PointerBuilder& pointer : mPointers) {
@@ -106,19 +109,17 @@
         }
 
         // 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();
+            resolvedCursorX = pointerCoords[0].getX();
+            resolvedCursorY = pointerCoords[0].getY();
         }
 
-        if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
-            addFlag(AMOTION_EVENT_FLAG_CANCELED);
-        }
-
-        return {InputEvent::nextId(),
+        return {mEventId,
                 mEventTime,
                 /*readTime=*/mEventTime,
                 mDeviceId,
@@ -137,13 +138,14 @@
                 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 @@
 
 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 @@
     }
 
     NotifyKeyArgs build() const {
-        return {InputEvent::nextId(),
+        return {mEventId,
                 mEventTime,
                 /*readTime=*/mEventTime,
                 mDeviceId,
@@ -222,6 +224,7 @@
     }
 
 private:
+    const int32_t mEventId;
     int32_t mAction;
     int32_t mDeviceId = DEFAULT_DEVICE_ID;
     uint32_t mSource;
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 782c84a..933c502 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -77,11 +77,11 @@
 
         // If a device is associated with a specific display but there is no
         // associated DisplayViewport, don't enable the device.
-        if (enable && (mAssociatedDisplayPort || mAssociatedDisplayUniqueId) &&
+        if (enable && (mAssociatedDisplayPort || mAssociatedDisplayUniqueIdByPort) &&
             !mAssociatedViewport) {
             const std::string desc = mAssociatedDisplayPort
                     ? "port " + std::to_string(*mAssociatedDisplayPort)
-                    : "uniqueId " + *mAssociatedDisplayUniqueId;
+                    : "uniqueId " + *mAssociatedDisplayUniqueIdByPort;
             ALOGW("Cannot enable input device %s because it is associated "
                   "with %s, but the corresponding viewport is not found",
                   getName().c_str(), desc.c_str());
@@ -124,9 +124,15 @@
     } else {
         dump += "<none>\n";
     }
-    dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueId: ");
-    if (mAssociatedDisplayUniqueId) {
-        dump += StringPrintf("%s\n", mAssociatedDisplayUniqueId->c_str());
+    dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueIdByPort: ");
+    if (mAssociatedDisplayUniqueIdByPort) {
+        dump += StringPrintf("%s\n", mAssociatedDisplayUniqueIdByPort->c_str());
+    } else {
+        dump += "<none>\n";
+    }
+    dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueIdByDescriptor: ");
+    if (mAssociatedDisplayUniqueIdByDescriptor) {
+        dump += StringPrintf("%s\n", mAssociatedDisplayUniqueIdByDescriptor->c_str());
     } else {
         dump += "<none>\n";
     }
@@ -269,8 +275,28 @@
 
             // In most situations, no port or name will be specified.
             mAssociatedDisplayPort = std::nullopt;
-            mAssociatedDisplayUniqueId = std::nullopt;
+            mAssociatedDisplayUniqueIdByPort = std::nullopt;
             mAssociatedViewport = std::nullopt;
+            // Find the display port that corresponds to the current input device descriptor
+            const std::string& inputDeviceDescriptor = mIdentifier.descriptor;
+            if (!inputDeviceDescriptor.empty()) {
+                const std::unordered_map<std::string, uint8_t>& ports =
+                        readerConfig.portAssociations;
+                const auto& displayPort = ports.find(inputDeviceDescriptor);
+                if (displayPort != ports.end()) {
+                    mAssociatedDisplayPort = std::make_optional(displayPort->second);
+                } else {
+                    const std::unordered_map<std::string, std::string>&
+                            displayUniqueIdsByDescriptor =
+                                    readerConfig.uniqueIdAssociationsByDescriptor;
+                    const auto& displayUniqueIdByDescriptor =
+                            displayUniqueIdsByDescriptor.find(inputDeviceDescriptor);
+                    if (displayUniqueIdByDescriptor != displayUniqueIdsByDescriptor.end()) {
+                        mAssociatedDisplayUniqueIdByDescriptor =
+                                displayUniqueIdByDescriptor->second;
+                    }
+                }
+            }
             // Find the display port that corresponds to the current input port.
             const std::string& inputPort = mIdentifier.location;
             if (!inputPort.empty()) {
@@ -280,11 +306,11 @@
                 if (displayPort != ports.end()) {
                     mAssociatedDisplayPort = std::make_optional(displayPort->second);
                 } else {
-                    const std::unordered_map<std::string, std::string>& displayUniqueIds =
-                            readerConfig.uniqueIdAssociations;
-                    const auto& displayUniqueId = displayUniqueIds.find(inputPort);
-                    if (displayUniqueId != displayUniqueIds.end()) {
-                        mAssociatedDisplayUniqueId = displayUniqueId->second;
+                    const std::unordered_map<std::string, std::string>& displayUniqueIdsByPort =
+                            readerConfig.uniqueIdAssociationsByPort;
+                    const auto& displayUniqueIdByPort = displayUniqueIdsByPort.find(inputPort);
+                    if (displayUniqueIdByPort != displayUniqueIdsByPort.end()) {
+                        mAssociatedDisplayUniqueIdByPort = displayUniqueIdByPort->second;
                     }
                 }
             }
@@ -299,13 +325,21 @@
                           "but the corresponding viewport is not found.",
                           getName().c_str(), *mAssociatedDisplayPort);
                 }
-            } else if (mAssociatedDisplayUniqueId != std::nullopt) {
-                mAssociatedViewport =
-                        readerConfig.getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId);
+            } else if (mAssociatedDisplayUniqueIdByDescriptor != std::nullopt) {
+                mAssociatedViewport = readerConfig.getDisplayViewportByUniqueId(
+                        *mAssociatedDisplayUniqueIdByDescriptor);
                 if (!mAssociatedViewport) {
                     ALOGW("Input device %s should be associated with display %s but the "
                           "corresponding viewport cannot be found",
-                          getName().c_str(), mAssociatedDisplayUniqueId->c_str());
+                          getName().c_str(), mAssociatedDisplayUniqueIdByDescriptor->c_str());
+                }
+            } else if (mAssociatedDisplayUniqueIdByPort != std::nullopt) {
+                mAssociatedViewport = readerConfig.getDisplayViewportByUniqueId(
+                        *mAssociatedDisplayUniqueIdByPort);
+                if (!mAssociatedViewport) {
+                    ALOGW("Input device %s should be associated with display %s but the "
+                          "corresponding viewport cannot be found",
+                          getName().c_str(), mAssociatedDisplayUniqueIdByPort->c_str());
                 }
             }
 
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 0719b0c..feb4071 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -63,8 +63,11 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mAssociatedDisplayPort;
     }
-    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
-        return mAssociatedDisplayUniqueId;
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByPort() const {
+        return mAssociatedDisplayUniqueIdByPort;
+    }
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByDescriptor() const {
+        return mAssociatedDisplayUniqueIdByDescriptor;
     }
     inline std::optional<std::string> getDeviceTypeAssociation() const {
         return mAssociatedDeviceType;
@@ -194,7 +197,8 @@
     bool mIsWaking;
     bool mIsExternal;
     std::optional<uint8_t> mAssociatedDisplayPort;
-    std::optional<std::string> mAssociatedDisplayUniqueId;
+    std::optional<std::string> mAssociatedDisplayUniqueIdByPort;
+    std::optional<std::string> mAssociatedDisplayUniqueIdByDescriptor;
     std::optional<std::string> mAssociatedDeviceType;
     std::optional<DisplayViewport> mAssociatedViewport;
     bool mHasMic;
@@ -449,8 +453,11 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mDevice.getAssociatedDisplayPort();
     }
-    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
-        return mDevice.getAssociatedDisplayUniqueId();
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByPort() const {
+        return mDevice.getAssociatedDisplayUniqueIdByPort();
+    }
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByDescriptor() const {
+        return mDevice.getAssociatedDisplayUniqueIdByDescriptor();
     }
     inline std::optional<std::string> getDeviceTypeAssociation() const {
         return mDevice.getDeviceTypeAssociation();
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 7d27d4a..b0ba8d8 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 @@
     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,14 +533,20 @@
  * 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();
         }
 
-        const std::optional<std::string> associatedDisplayUniqueId =
-                getDeviceContext().getAssociatedDisplayUniqueId();
-        if (associatedDisplayUniqueId) {
+        const std::optional<std::string> associatedDisplayUniqueIdByDescriptor =
+                getDeviceContext().getAssociatedDisplayUniqueIdByDescriptor();
+        if (associatedDisplayUniqueIdByDescriptor) {
+            return getDeviceContext().getAssociatedViewport();
+        }
+
+        const std::optional<std::string> associatedDisplayUniqueIdByPort =
+                getDeviceContext().getAssociatedDisplayUniqueIdByPort();
+        if (associatedDisplayUniqueIdByPort) {
             return getDeviceContext().getAssociatedViewport();
         }
 
@@ -939,8 +947,10 @@
         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 +1894,7 @@
     }
 
     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 +1921,7 @@
         // 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 4b39e40..8451675 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 @@
     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 721cdfd..f558ba1 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -185,6 +185,7 @@
     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 @@
     }
 
     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 a544fa3..6df339e 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -396,14 +396,16 @@
         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 @@
             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 @@
 
         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()
         }
 
-        pub fn is_finish_called(&self) -> bool {
-            self.0.read().unwrap().is_finish_called
+        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 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 2d503ae..96e5681 100644
--- a/services/inputflinger/rust/input_filter_thread.rs
+++ b/services/inputflinger/rust/input_filter_thread.rs
@@ -33,8 +33,6 @@
 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 @@
     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 @@
             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 @@
     /// 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 @@
 
     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 @@
                     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 @@
 
 #[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.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 + 100) * 1000000);
 
-        test_thread.move_time_forward(100);
-
-        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 09fbf40..0f18a2f 100644
--- a/services/inputflinger/rust/slow_keys_filter.rs
+++ b/services/inputflinger/rust/slow_keys_filter.rs
@@ -207,13 +207,19 @@
 
 #[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 @@
         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 @@
     #[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 @@
     #[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();
+        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
+        });
 
-        filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
-        test_thread.dispatch_next();
+        std::thread::sleep(Duration::from_nanos(SLOW_KEYS_THRESHOLD_NS as u64 / 2));
 
-        test_thread.move_time_forward(10);
+        now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::UP,
+            downTime: now,
+            eventTime: now,
+            ..BASE_KEY_EVENT
+        });
 
-        filter.notify_key(&KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT });
-        test_thread.dispatch_next();
-
-        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();
+        std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
 
-        test_thread.move_time_forward(100);
-
-        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 @@
 
     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 @@
 
     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 6ae9790..9b5db23 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -67,6 +67,8 @@
         "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 e231bcc..1360cd0 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -466,8 +466,15 @@
     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 d83924f..2cc018e 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -115,6 +115,7 @@
     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 @@
     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/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 8f593b5..e9118a9 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -139,7 +139,7 @@
 
 void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId,
                                                         const std::string& displayUniqueId) {
-    mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
+    mConfig.uniqueIdAssociationsByPort.insert({inputUniqueId, displayUniqueId});
 }
 
 void FakeInputReaderPolicy::addKeyboardLayoutAssociation(const std::string& inputUniqueId,
diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h
index c0c8975..26c2b4b 100644
--- a/services/inputflinger/tests/FakeWindows.h
+++ b/services/inputflinger/tests/FakeWindows.h
@@ -157,6 +157,16 @@
 
     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 @@
 
     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/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 62a9235..ccd28f3 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -4800,6 +4800,96 @@
 }
 
 /**
+ * Three windows:
+ * - Left window
+ * - Right window
+ * - Outside window(watch for ACTION_OUTSIDE events)
+ * The windows "left" and "outside" share the same owner, the window "right" has a different owner,
+ * In order to allow the outside window can receive the ACTION_OUTSIDE events, the outside window is
+ * positioned above the "left" and "right" windows, and it doesn't overlap with them.
+ *
+ * First, device A report a down event landed in the right window, the outside window can receive
+ * an ACTION_OUTSIDE event that with zeroed coordinates, the device B report a down event landed
+ * in the left window, the outside window can receive an ACTION_OUTSIDE event the with valid
+ * coordinates, after these, device A and device B continue report MOVE event, the right and left
+ * window can receive it, but outside window event can't receive it.
+ */
+TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinatesWhenMultiDevice) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect{0, 0, 100, 100});
+    leftWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+
+    sp<FakeWindowHandle> outsideWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Outside Window",
+                                       ADISPLAY_ID_DEFAULT);
+    outsideWindow->setFrame(Rect{100, 100, 200, 200});
+    outsideWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+    outsideWindow->setWatchOutsideTouch(true);
+
+    std::shared_ptr<FakeApplicationHandle> anotherApplication =
+            std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(anotherApplication, mDispatcher, "Right Window",
+                                       ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect{100, 0, 200, 100});
+    rightWindow->setOwnerInfo(gui::Pid{2}, gui::Uid{202});
+
+    // OutsideWindow must be above left window and right window to receive ACTION_OUTSIDE events
+    // when left window or right window is tapped
+    mDispatcher->onWindowInfosChanged(
+            {{*outsideWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    // Tap on right window use device A
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    // Right window is belonged to another owner, so outsideWindow should receive ACTION_OUTSIDE
+    // with zeroed coords.
+    outsideWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_OUTSIDE), WithDeviceId(deviceA), WithCoords(0, 0)));
+
+    // Tap on left window use device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    rightWindow->assertNoEvents();
+    // Because new gesture down on the left window that has the same owner with outside Window, the
+    // outside Window should receive the ACTION_OUTSIDE with coords.
+    outsideWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_OUTSIDE), WithDeviceId(deviceB), WithCoords(-50, -50)));
+
+    // Ensure that windows that can only accept outside do not receive remaining gestures
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(51))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceA)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(51).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+    rightWindow->assertNoEvents();
+    outsideWindow->assertNoEvents();
+}
+
+/**
  * This test documents the behavior of WATCH_OUTSIDE_TOUCH. The window will get ACTION_OUTSIDE when
  * a another pointer causes ACTION_DOWN to be sent to another window for the first time. Only one
  * ACTION_OUTSIDE event is sent per gesture.
@@ -7391,6 +7481,60 @@
     spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithEventId(notifyArgs.id)));
 }
 
+/**
+ * When a device reports a DOWN event, which lands in a window that supports splits, and then the
+ * device then reports a POINTER_DOWN, which lands in the location of a non-existing window, then
+ * the previous window should receive this event and not be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, SingleDevicePointerDownEventRetentionWithoutWindowTarget) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(200).y(200))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(POINTER_1_DOWN)));
+}
+
+/**
+ * When deviceA reports a DOWN event, which lands in a window that supports splits, and then deviceB
+ * also reports a DOWN event, which lands in the location of a non-existing window, then the
+ * previous window should receive deviceB's event and it should be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, SecondDeviceDownEventDroppedWithoutWindowTarget) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
+                                      .deviceId(deviceB)
+                                      .build());
+    window->assertNoEvents();
+}
+
 class InputDispatcherFallbackKeyTest : public InputDispatcherTest {
 protected:
     std::shared_ptr<FakeApplicationHandle> mApp;
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 1d46c9a..a48c5a9 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2907,7 +2907,7 @@
     const auto initialGeneration = mDevice->getGeneration();
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                  InputReaderConfiguration::Change::DISPLAY_INFO);
-    ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId());
+    ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueIdByPort());
     ASSERT_GT(mDevice->getGeneration(), initialGeneration);
     ASSERT_EQ(mDevice->getDeviceInfo().getAssociatedDisplayId(), SECONDARY_DISPLAY_ID);
 }
@@ -9790,163 +9790,13 @@
     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 @@
     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 @@
 
     // 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 0000000..32acb5f
--- /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 0000000..ed20bc8
--- /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 0000000..23fa045
--- /dev/null
+++ b/services/inputflinger/tests/InputTracingTest.cpp
@@ -0,0 +1,734 @@
+/*
+ * 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});
+
+    waitForTracerIdle();
+}
+
+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 0e3fdbb..10dc927 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 @@
         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 39fce2b..03758b3 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, 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 35d0ffb..a1210b4 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_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_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_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 @@
 };
 
 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_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, 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 0aee7d4..ffc1dd7 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 @@
 racarr@google.com
 ramindani@google.com
 rnlee@google.com
+sallyqi@google.com
 scroggo@google.com
 vishnun@google.com
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 96eccf2..6b65449 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -235,7 +235,8 @@
         ParcelableVsyncEventData* outVsyncEventData) {
     ATRACE_CALL();
     outVsyncEventData->vsync =
-            mEventThread->getLatestVsyncEventData(sp<EventThreadConnection>::fromExisting(this));
+            mEventThread->getLatestVsyncEventData(sp<EventThreadConnection>::fromExisting(this),
+                                                  systemTime());
     return binder::Status::ok();
 }
 
@@ -387,8 +388,8 @@
     }
 }
 
-VsyncEventData EventThread::getLatestVsyncEventData(
-        const sp<EventThreadConnection>& connection) const {
+VsyncEventData EventThread::getLatestVsyncEventData(const sp<EventThreadConnection>& connection,
+                                                    nsecs_t now) const {
     // Resync so that the vsync is accurate with hardware. getLatestVsyncEventData is an alternate
     // way to get vsync data (instead of posting callbacks to Choreographer).
     mCallback.resync();
@@ -399,11 +400,10 @@
     const auto [presentTime, deadline] = [&]() -> std::pair<nsecs_t, nsecs_t> {
         std::lock_guard<std::mutex> lock(mMutex);
         const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom(
-                systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
+                now + mWorkDuration.get().count() + mReadyDuration.count());
         return {vsyncTime, vsyncTime - mReadyDuration.count()};
     }();
-    generateFrameTimeline(vsyncEventData, frameInterval.ns(), systemTime(SYSTEM_TIME_MONOTONIC),
-                          presentTime, deadline);
+    generateFrameTimeline(vsyncEventData, frameInterval.ns(), now, presentTime, deadline);
     if (FlagManager::getInstance().vrr_config()) {
         mCallback.onExpectedPresentTimePosted(TimePoint::fromNs(presentTime));
     }
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 90e61a9..f772126 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -127,8 +127,8 @@
     virtual void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) = 0;
     // Requests the next vsync. If resetIdleTimer is set to true, it resets the idle timer.
     virtual void requestNextVsync(const sp<EventThreadConnection>& connection) = 0;
-    virtual VsyncEventData getLatestVsyncEventData(
-            const sp<EventThreadConnection>& connection) const = 0;
+    virtual VsyncEventData getLatestVsyncEventData(const sp<EventThreadConnection>& connection,
+                                                   nsecs_t now) const = 0;
 
     virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0;
 
@@ -160,8 +160,8 @@
     status_t registerDisplayEventConnection(const sp<EventThreadConnection>& connection) override;
     void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) override;
     void requestNextVsync(const sp<EventThreadConnection>& connection) override;
-    VsyncEventData getLatestVsyncEventData(
-            const sp<EventThreadConnection>& connection) const override;
+    VsyncEventData getLatestVsyncEventData(const sp<EventThreadConnection>& connection,
+                                           nsecs_t now) const override;
 
     void enableSyntheticVsync(bool) override;
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 1bc4ac2..5f17128 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -55,10 +55,10 @@
                                    bool pendingModeChange, const LayerProps& props) {
     lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
 
-    mLastUpdatedTime = std::max(lastPresentTime, now);
     *mLayerProps = props;
     switch (updateType) {
         case LayerUpdateType::AnimationTX:
+            mLastUpdatedTime = std::max(lastPresentTime, now);
             mLastAnimationTime = std::max(lastPresentTime, now);
             break;
         case LayerUpdateType::SetFrameRate:
@@ -67,6 +67,7 @@
             }
             FALLTHROUGH_INTENDED;
         case LayerUpdateType::Buffer:
+            mLastUpdatedTime = std::max(lastPresentTime, now);
             FrameTimeData frameTime = {.presentTime = lastPresentTime,
                                        .queueTime = mLastUpdatedTime,
                                        .pendingModeChange = pendingModeChange,
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index c83d81f..005ec05 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -663,13 +663,7 @@
 
 void Scheduler::recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime,
                                    nsecs_t now, LayerHistory::LayerUpdateType updateType) {
-    const auto& selectorPtr = pacesetterSelectorPtr();
-    // Skip recording layer history on LayerUpdateType::SetFrameRate for MRR devices when the
-    // dVRR vote types are guarded (disabled) for MRR. This is to avoid activity when setting dVRR
-    // vote types.
-    if (selectorPtr->canSwitch() &&
-        (updateType != LayerHistory::LayerUpdateType::SetFrameRate ||
-         layerProps.setFrameRateVote.isVoteValidForMrr(selectorPtr->isVrrDevice()))) {
+    if (pacesetterSelectorPtr()->canSwitch()) {
         mLayerHistory.record(id, layerProps, presentTime, now, updateType);
     }
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 7b313c3..21f1cb3 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 @@
                                                              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 @@
         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/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 3eabe1f..625d2e6 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -493,7 +493,7 @@
     EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_, _))
             .WillOnce(Return(preferredExpectedPresentationTime));
 
-    VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection);
+    VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection, now);
 
     // Check EventThread immediately requested a resync.
     EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index e2b0ed1..8dd1a34 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -55,7 +55,7 @@
                 (override));
     MOCK_METHOD(void, requestNextVsync, (const sp<android::EventThreadConnection>&), (override));
     MOCK_METHOD(VsyncEventData, getLatestVsyncEventData,
-                (const sp<android::EventThreadConnection>&), (const, override));
+                (const sp<android::EventThreadConnection>&, nsecs_t), (const, override));
     MOCK_METHOD(void, requestLatestConfig, (const sp<android::EventThreadConnection>&));
     MOCK_METHOD(void, pauseVsyncCallback, (bool));
     MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr<scheduler::VsyncSchedule>), (override));
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
index 9b95d74..15fde91 100644
--- a/services/vibratorservice/test/VibratorHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -255,16 +255,17 @@
                 .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 1933a11..b584f64 100644
--- a/services/vibratorservice/test/test_utils.h
+++ b/services/vibratorservice/test/test_utils.h
@@ -85,10 +85,36 @@
     ~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_