Merge "Add a sysprop to switch between blur algorithms" into main
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/include/android/performance_hint.h b/include/android/performance_hint.h
index 3c82d88..97e4dc0 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -114,6 +114,9 @@
  * API, the client is expected to call {@link APerformanceHint_reportActualWorkDuration} each
  * cycle to report the actual time taken to complete to the system.
  *
+ * Note, methods of {@link APerformanceHintSession_*} are not thread safe so callers must
+ * ensure thread safety.
+ *
  * All timings should be from `std::chrono::steady_clock` or `clock_gettime(CLOCK_MONOTONIC, ...)`
  */
 typedef struct APerformanceHintSession APerformanceHintSession;
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 9c24dc5..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",
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 0540ed3..0f9c58c 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -100,9 +100,9 @@
 mod native;
 mod parcel;
 mod proxy;
-#[cfg(not(target_os = "trusty"))]
+#[cfg(not(trusty))]
 mod service;
-#[cfg(not(target_os = "trusty"))]
+#[cfg(not(trusty))]
 mod state;
 
 use binder_ndk_sys as sys;
@@ -112,13 +112,13 @@
 pub use error::{ExceptionCode, IntoBinderResult, Status, StatusCode};
 pub use parcel::{ParcelFileDescriptor, Parcelable, ParcelableHolder};
 pub use proxy::{DeathRecipient, SpIBinder, WpIBinder};
-#[cfg(not(target_os = "trusty"))]
+#[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 da9d7dc..c87cc94 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -327,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,
@@ -374,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,
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/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/main.rs b/libs/binder/trusty/rust/binder_rpc_test/main.rs
index a71ce2c..3c1e784 100644
--- a/libs/binder/trusty/rust/binder_rpc_test/main.rs
+++ b/libs/binder/trusty/rust/binder_rpc_test/main.rs
@@ -23,14 +23,21 @@
 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() -> Strong<dyn IBinderRpcTest> {
-    let port = CString::try_new(SERVICE_PORT).expect("Failed to allocate port name");
+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();
+    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/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 241e668..833d209 100644
--- a/libs/binder/trusty/usertests-inc.mk
+++ b/libs/binder/trusty/usertests-inc.mk
@@ -16,6 +16,7 @@
 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/gui/LayerStatePermissions.cpp b/libs/gui/LayerStatePermissions.cpp
index 28697ca..c467cfd 100644
--- a/libs/gui/LayerStatePermissions.cpp
+++ b/libs/gui/LayerStatePermissions.cpp
@@ -23,31 +23,31 @@
 #include <gui/LayerState.h>
 
 namespace android {
-std::unordered_map<std::string, int> LayerStatePermissions::mPermissionMap = {
+std::vector<std::pair<String16, int>> LayerStatePermissions::mPermissionMap = {
         // If caller has ACCESS_SURFACE_FLINGER, they automatically get ROTATE_SURFACE_FLINGER
         // permission, as well
-        {"android.permission.ACCESS_SURFACE_FLINGER",
+        {String16("android.permission.ACCESS_SURFACE_FLINGER"),
          layer_state_t::Permission::ACCESS_SURFACE_FLINGER |
                  layer_state_t::Permission::ROTATE_SURFACE_FLINGER},
-        {"android.permission.ROTATE_SURFACE_FLINGER",
+        {String16("android.permission.ROTATE_SURFACE_FLINGER"),
          layer_state_t::Permission::ROTATE_SURFACE_FLINGER},
-        {"android.permission.INTERNAL_SYSTEM_WINDOW",
+        {String16("android.permission.INTERNAL_SYSTEM_WINDOW"),
          layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW},
 };
 
-static bool callingThreadHasPermission(const std::string& permission __attribute__((unused)),
+static bool callingThreadHasPermission(const String16& permission __attribute__((unused)),
                                        int pid __attribute__((unused)),
                                        int uid __attribute__((unused))) {
 #ifndef __ANDROID_VNDK__
     return uid == AID_GRAPHICS || uid == AID_SYSTEM ||
-            PermissionCache::checkPermission(String16(permission.c_str()), pid, uid);
+            PermissionCache::checkPermission(permission, pid, uid);
 #endif // __ANDROID_VNDK__
     return false;
 }
 
 uint32_t LayerStatePermissions::getTransactionPermissions(int pid, int uid) {
     uint32_t permissions = 0;
-    for (auto [permissionName, permissionVal] : mPermissionMap) {
+    for (const auto& [permissionName, permissionVal] : mPermissionMap) {
         if (callingThreadHasPermission(permissionName, pid, uid)) {
             permissions |= permissionVal;
         }
diff --git a/libs/gui/include/gui/LayerStatePermissions.h b/libs/gui/include/gui/LayerStatePermissions.h
index a90f30c..b6588a2 100644
--- a/libs/gui/include/gui/LayerStatePermissions.h
+++ b/libs/gui/include/gui/LayerStatePermissions.h
@@ -15,15 +15,14 @@
  */
 
 #include <stdint.h>
-#include <string>
-#include <unordered_map>
-
+#include <utils/String16.h>
+#include <vector>
 namespace android {
 class LayerStatePermissions {
 public:
     static uint32_t getTransactionPermissions(int pid, int uid);
 
 private:
-    static std::unordered_map<std::string, int> mPermissionMap;
+    static std::vector<std::pair<String16, int>> mPermissionMap;
 };
 } // namespace android
\ No newline at end of file
diff --git a/libs/gui/rust/aidl_types/src/lib.rs b/libs/gui/rust/aidl_types/src/lib.rs
index 3d29529..fead018 100644
--- a/libs/gui/rust/aidl_types/src/lib.rs
+++ b/libs/gui/rust/aidl_types/src/lib.rs
@@ -24,7 +24,7 @@
     ($name:ident) => {
         /// Unimplemented stub parcelable.
         #[derive(Debug, Default)]
-        pub struct $name(Option<()>);
+        pub struct $name(());
 
         impl UnstructuredParcelable for $name {
             fn write_to_parcel(&self, _parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
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/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp
index 48dc77e..e778884 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.cpp
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -30,7 +30,7 @@
 #include "SkCanvas.h"
 #include "SkRect.h"
 #include "SkTypeface.h"
-#include "src/utils/SkMultiPictureDocument.h"
+#include "include/docs/SkMultiPictureDocument.h"
 #include <sys/stat.h>
 
 namespace android {
@@ -196,7 +196,7 @@
         // procs doesn't need to outlive this Make call
         // The last argument is a callback for the endPage behavior.
         // See SkSharingProc.h for more explanation of this callback.
-        mMultiPic = SkMakeMultiPictureDocument(
+        mMultiPic = SkMultiPictureDocument::Make(
                 mOpenMultiPicStream.get(), &procs,
                 [sharingCtx = mSerialContext.get()](const SkPicture* pic) {
                     SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 312a1e6..12230f9 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -277,13 +277,3 @@
     "tests",
     "tools",
 ]
-
-filegroup {
-    name: "libui_host_common",
-    srcs: [
-        "Rect.cpp",
-        "Region.cpp",
-        "PixelFormat.cpp",
-        "Transform.cpp",
-    ],
-}
diff --git a/libs/ui/DebugUtils.cpp b/libs/ui/DebugUtils.cpp
index 8675f14..bee58e5 100644
--- a/libs/ui/DebugUtils.cpp
+++ b/libs/ui/DebugUtils.cpp
@@ -22,14 +22,12 @@
 #include <android-base/stringprintf.h>
 #include <string>
 
-using android::base::StringAppendF;
 using android::base::StringPrintf;
 using android::ui::ColorMode;
 using android::ui::RenderIntent;
 
-std::string decodeStandard(android_dataspace dataspace) {
-    const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
-    switch (dataspaceSelect) {
+std::string decodeStandardOnly(uint32_t dataspaceStandard) {
+    switch (dataspaceStandard) {
         case HAL_DATASPACE_STANDARD_BT709:
             return std::string("BT709");
 
@@ -62,63 +60,44 @@
 
         case HAL_DATASPACE_STANDARD_ADOBE_RGB:
             return std::string("AdobeRGB");
-
-        case 0:
-            switch (dataspace & 0xffff) {
-                case HAL_DATASPACE_JFIF:
-                    return std::string("(deprecated) JFIF (BT601_625)");
-
-                case HAL_DATASPACE_BT601_625:
-                    return std::string("(deprecated) BT601_625");
-
-                case HAL_DATASPACE_BT601_525:
-                    return std::string("(deprecated) BT601_525");
-
-                case HAL_DATASPACE_SRGB_LINEAR:
-                case HAL_DATASPACE_SRGB:
-                    return std::string("(deprecated) sRGB");
-
-                case HAL_DATASPACE_BT709:
-                    return std::string("(deprecated) BT709");
-
-                case HAL_DATASPACE_ARBITRARY:
-                    return std::string("ARBITRARY");
-
-                case HAL_DATASPACE_UNKNOWN:
-                // Fallthrough
-                default:
-                    return StringPrintf("Unknown deprecated dataspace code %d", dataspace);
-            }
     }
 
-    return StringPrintf("Unknown dataspace code %d", dataspaceSelect);
+    return StringPrintf("Unknown dataspace code %d", dataspaceStandard);
 }
 
-std::string decodeTransfer(android_dataspace dataspace) {
-    const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
-    if (dataspaceSelect == 0) {
+std::string decodeStandard(android_dataspace dataspace) {
+    const uint32_t dataspaceStandard = (dataspace & HAL_DATASPACE_STANDARD_MASK);
+    if (dataspaceStandard == 0) {
         switch (dataspace & 0xffff) {
             case HAL_DATASPACE_JFIF:
+                return std::string("(deprecated) JFIF (BT601_625)");
+
             case HAL_DATASPACE_BT601_625:
+                return std::string("(deprecated) BT601_625");
+
             case HAL_DATASPACE_BT601_525:
-            case HAL_DATASPACE_BT709:
-                return std::string("SMPTE_170M");
+                return std::string("(deprecated) BT601_525");
 
             case HAL_DATASPACE_SRGB_LINEAR:
-            case HAL_DATASPACE_ARBITRARY:
-                return std::string("Linear");
-
             case HAL_DATASPACE_SRGB:
-                return std::string("sRGB");
+                return std::string("(deprecated) sRGB");
+
+            case HAL_DATASPACE_BT709:
+                return std::string("(deprecated) BT709");
+
+            case HAL_DATASPACE_ARBITRARY:
+                return std::string("ARBITRARY");
 
             case HAL_DATASPACE_UNKNOWN:
             // Fallthrough
             default:
-                return std::string("");
+                return StringPrintf("Unknown deprecated dataspace code %d", dataspace);
         }
     }
+    return decodeStandardOnly(dataspaceStandard);
+}
 
-    const uint32_t dataspaceTransfer = (dataspace & HAL_DATASPACE_TRANSFER_MASK);
+std::string decodeTransferOnly(uint32_t dataspaceTransfer) {
     switch (dataspaceTransfer) {
         case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
             return std::string("Unspecified");
@@ -151,6 +130,52 @@
     return StringPrintf("Unknown dataspace transfer %d", dataspaceTransfer);
 }
 
+std::string decodeTransfer(android_dataspace dataspace) {
+    const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
+    if (dataspaceSelect == 0) {
+        switch (dataspace & 0xffff) {
+            case HAL_DATASPACE_JFIF:
+            case HAL_DATASPACE_BT601_625:
+            case HAL_DATASPACE_BT601_525:
+            case HAL_DATASPACE_BT709:
+                return std::string("SMPTE_170M");
+
+            case HAL_DATASPACE_SRGB_LINEAR:
+            case HAL_DATASPACE_ARBITRARY:
+                return std::string("Linear");
+
+            case HAL_DATASPACE_SRGB:
+                return std::string("sRGB");
+
+            case HAL_DATASPACE_UNKNOWN:
+            // Fallthrough
+            default:
+                return std::string("");
+        }
+    }
+
+    const uint32_t dataspaceTransfer = (dataspace & HAL_DATASPACE_TRANSFER_MASK);
+    return decodeTransferOnly(dataspaceTransfer);
+}
+
+std::string decodeRangeOnly(uint32_t dataspaceRange) {
+    switch (dataspaceRange) {
+        case HAL_DATASPACE_RANGE_UNSPECIFIED:
+            return std::string("Range Unspecified");
+
+        case HAL_DATASPACE_RANGE_FULL:
+            return std::string("Full range");
+
+        case HAL_DATASPACE_RANGE_LIMITED:
+            return std::string("Limited range");
+
+        case HAL_DATASPACE_RANGE_EXTENDED:
+            return std::string("Extended range");
+    }
+
+    return StringPrintf("Unknown dataspace range %d", dataspaceRange);
+}
+
 std::string decodeRange(android_dataspace dataspace) {
     const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
     if (dataspaceSelect == 0) {
@@ -174,21 +199,7 @@
     }
 
     const uint32_t dataspaceRange = (dataspace & HAL_DATASPACE_RANGE_MASK);
-    switch (dataspaceRange) {
-        case HAL_DATASPACE_RANGE_UNSPECIFIED:
-            return std::string("Range Unspecified");
-
-        case HAL_DATASPACE_RANGE_FULL:
-            return std::string("Full range");
-
-        case HAL_DATASPACE_RANGE_LIMITED:
-            return std::string("Limited range");
-
-        case HAL_DATASPACE_RANGE_EXTENDED:
-            return std::string("Extended range");
-    }
-
-    return StringPrintf("Unknown dataspace range %d", dataspaceRange);
+    return decodeRangeOnly(dataspaceRange);
 }
 
 std::string dataspaceDetails(android_dataspace dataspace) {
diff --git a/libs/ui/include/ui/DebugUtils.h b/libs/ui/include/ui/DebugUtils.h
index 18cd487..7c4ac42 100644
--- a/libs/ui/include/ui/DebugUtils.h
+++ b/libs/ui/include/ui/DebugUtils.h
@@ -27,8 +27,11 @@
 }
 
 std::string decodeStandard(android_dataspace dataspace);
+std::string decodeStandardOnly(uint32_t dataspaceStandard);
 std::string decodeTransfer(android_dataspace dataspace);
+std::string decodeTransferOnly(uint32_t dataspaceTransfer);
 std::string decodeRange(android_dataspace dataspace);
+std::string decodeRangeOnly(uint32_t dataspaceRange);
 std::string dataspaceDetails(android_dataspace dataspace);
 std::string decodeColorMode(android::ui::ColorMode colormode);
 std::string decodeColorTransform(android_color_transform colorTransform);
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/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/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/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 8d91599..b0ba8d8 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -538,9 +538,15 @@
             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();
         }
 
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 367bc70..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);
 }
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/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index dc86577..3446f58 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -461,16 +461,18 @@
         // is pre-Q, still permit delivering events to the app even if permission isn't granted
         // (since this permission was only introduced in Q)
         if ((event.type == SENSOR_TYPE_STEP_COUNTER || event.type == SENSOR_TYPE_STEP_DETECTOR) &&
-                mTargetSdk > 0 && mTargetSdk <= __ANDROID_API_P__) {
+            mTargetSdk > 0 && mTargetSdk <= __ANDROID_API_P__) {
+            success = true;
+        } else if (mUid == AID_SYSTEM) {
+            // Allow access if it is requested from system.
             success = true;
         } else {
             int32_t sensorHandle = event.sensor;
             String16 noteMsg("Sensor event (");
             noteMsg.append(String16(mService->getSensorStringType(sensorHandle)));
             noteMsg.append(String16(")"));
-            int32_t appOpMode = mService->sAppOpsManager.noteOp(iter->second, mUid,
-                                                                mOpPackageName, mAttributionTag,
-                                                                noteMsg);
+            int32_t appOpMode = mService->sAppOpsManager.noteOp(iter->second, mUid, mOpPackageName,
+                                                                mAttributionTag, noteMsg);
             success = (appOpMode == AppOpsManager::MODE_ALLOWED);
         }
     }
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index e1c43c6..69e4309 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -2302,11 +2302,16 @@
         // requirement to hold the AR permission to access Step Counter and Step Detector events
         // was introduced.
         canAccess = true;
+    } else if (IPCThreadState::self()->getCallingUid() == AID_SYSTEM) {
+        // Allow access if it is requested from system.
+        canAccess = true;
     } else if (hasPermissionForSensor(sensor)) {
-        // Ensure that the AppOp is allowed, or that there is no necessary app op for the sensor
+        // Ensure that the AppOp is allowed, or that there is no necessary app op
+        // for the sensor
         if (opCode >= 0) {
-            const int32_t appOpMode = sAppOpsManager.checkOp(opCode,
-                    IPCThreadState::self()->getCallingUid(), opPackageName);
+            const int32_t appOpMode =
+                    sAppOpsManager.checkOp(opCode, IPCThreadState::self()->getCallingUid(),
+                                           opPackageName);
             canAccess = (appOpMode == AppOpsManager::MODE_ALLOWED);
         } else {
             canAccess = true;
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 575a30e..4612117 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -126,6 +126,7 @@
                        const std::unordered_map<std::string, bool>&());
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
+    MOCK_CONST_METHOD1(dumpOverlayProperties, void(std::string&));
     MOCK_CONST_METHOD0(getComposer, android::Hwc2::Composer*());
 
     MOCK_METHOD(hal::HWDisplayId, getPrimaryHwcDisplayId, (), (const, override));
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 776bcd3..21d49f8 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -77,9 +77,7 @@
 using aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
-using aidl::android::hardware::graphics::composer3::VrrConfig;
 using namespace std::string_literals;
-namespace hal = android::hardware::graphics::composer::hal;
 
 namespace android {
 
@@ -964,8 +962,45 @@
     return mSupportedLayerGenericMetadata;
 }
 
+void HWComposer::dumpOverlayProperties(std::string& result) const {
+    // dump overlay properties
+    result.append("OverlayProperties:\n");
+    base::StringAppendF(&result, "supportMixedColorSpaces: %d\n",
+                        mOverlayProperties.supportMixedColorSpaces);
+    base::StringAppendF(&result, "SupportedBufferCombinations(%zu entries)\n",
+                        mOverlayProperties.combinations.size());
+    for (const auto& combination : mOverlayProperties.combinations) {
+        result.append("    pixelFormats=\n");
+        for (const auto& pixelFormat : combination.pixelFormats) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodePixelFormat(static_cast<PixelFormat>(pixelFormat)).c_str(),
+                                static_cast<uint32_t>(pixelFormat));
+        }
+        result.append("    standards=\n");
+        for (const auto& standard : combination.standards) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodeStandard(static_cast<android_dataspace>(standard)).c_str(),
+                                static_cast<uint32_t>(standard));
+        }
+        result.append("    transfers=\n");
+        for (const auto& transfer : combination.transfers) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodeTransferOnly(static_cast<uint32_t>(transfer)).c_str(),
+                                static_cast<uint32_t>(transfer));
+        }
+        result.append("    ranges=\n");
+        for (const auto& range : combination.ranges) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodeRangeOnly(static_cast<uint32_t>(range)).c_str(),
+                                static_cast<uint32_t>(range));
+        }
+        result.append("\n");
+    }
+}
+
 void HWComposer::dump(std::string& result) const {
     result.append(mComposer->dumpDebugInfo());
+    dumpOverlayProperties(result);
 }
 
 std::optional<PhysicalDisplayId> HWComposer::toPhysicalDisplayId(
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 7fbbb1a..bc32cda 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -269,6 +269,8 @@
 
     virtual void dump(std::string& out) const = 0;
 
+    virtual void dumpOverlayProperties(std::string& out) const = 0;
+
     virtual Hwc2::Composer* getComposer() const = 0;
 
     // Returns the first display connected at boot. Its connection via HWComposer::onHotplug,
@@ -468,6 +470,7 @@
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
+    void dumpOverlayProperties(std::string& out) const override;
 
     Hwc2::Composer* getComposer() const override { return mComposer.get(); }
 
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/tests/OWNERS b/services/surfaceflinger/tests/OWNERS
new file mode 100644
index 0000000..1878326
--- /dev/null
+++ b/services/surfaceflinger/tests/OWNERS
@@ -0,0 +1,5 @@
+per-file HdrSdrRatioOverlay_test.cpp = set noparent
+per-file HdrSdrRatioOverlay_test.cpp = alecmouri@google.com, sallyqi@google.com, jreck@google.com
+
+per-file Layer* = set noparent
+per-file Layer* = pdwilliams@google.com, vishnun@google.com, melodymhsu@google.com
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
index 22b72f9..f2e2c8a 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
@@ -33,24 +33,45 @@
 TEST_F(DisplayTransactionTest, notifyPowerBoostNotifiesTouchEvent) {
     using namespace std::chrono_literals;
 
+    std::mutex timerMutex;
+    std::condition_variable cv;
+
     injectDefaultInternalDisplay([](FakeDisplayDeviceInjector&) {});
 
-    mFlinger.scheduler()->replaceTouchTimer(100);
-    std::this_thread::sleep_for(10ms);                  // wait for callback to be triggered
+    std::unique_lock lock(timerMutex);
+    bool didReset = false; // keeps track of what the most recent call was
+
+    auto waitForTimerReset = [&] { cv.wait_for(lock, 100ms, [&] { return didReset; }); };
+    auto waitForTimerExpired = [&] { cv.wait_for(lock, 100ms, [&] { return !didReset; }); };
+
+    // Add extra logic to unblock the test when the timer callbacks get called
+    mFlinger.scheduler()->replaceTouchTimer(10, [&](bool isReset) {
+        {
+            std::unique_lock lock(timerMutex); // guarantee we're waiting on the cv
+            didReset = isReset;
+        }
+        cv.notify_one();                   // wake the cv
+        std::unique_lock lock(timerMutex); // guarantee we finished the cv logic
+    });
+
+    waitForTimerReset();
     EXPECT_TRUE(mFlinger.scheduler()->isTouchActive()); // Starting timer activates touch
 
-    std::this_thread::sleep_for(110ms); // wait for reset touch timer to expire and trigger callback
-    EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
+    waitForTimerExpired();
+    EXPECT_FALSE(mFlinger.scheduler()->isTouchActive()); // Stopping timer deactivates touch
 
     EXPECT_EQ(NO_ERROR, mFlinger.notifyPowerBoost(static_cast<int32_t>(Boost::CAMERA_SHOT)));
-    std::this_thread::sleep_for(10ms); // wait for callback to maybe be triggered
-    EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
 
-    std::this_thread::sleep_for(110ms); // wait for reset touch timer to expire and trigger callback
+    EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
+    // Wait for the timer to start just in case
+    waitForTimerReset();
+    EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
+    // Wait for the timer to stop, again just in case
+    waitForTimerExpired();
     EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
 
     EXPECT_EQ(NO_ERROR, mFlinger.notifyPowerBoost(static_cast<int32_t>(Boost::INTERACTION)));
-    std::this_thread::sleep_for(10ms); // wait for callback to be triggered.
+    waitForTimerReset();
     EXPECT_TRUE(mFlinger.scheduler()->isTouchActive());
 }
 
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 1472ebf..1e02c67 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -140,14 +140,25 @@
         return mLayerHistory.mActiveLayerInfos.size();
     }
 
-    void replaceTouchTimer(int64_t millis) {
+    void replaceTouchTimer(int64_t millis,
+                           std::function<void(bool isReset)>&& testCallback = nullptr) {
         if (mTouchTimer) {
             mTouchTimer.reset();
         }
         mTouchTimer.emplace(
                 "Testable Touch timer", std::chrono::milliseconds(millis),
-                [this] { touchTimerCallback(TimerState::Reset); },
-                [this] { touchTimerCallback(TimerState::Expired); });
+                [this, testCallback] {
+                    touchTimerCallback(TimerState::Reset);
+                    if (testCallback != nullptr) {
+                        testCallback(true);
+                    }
+                },
+                [this, testCallback] {
+                    touchTimerCallback(TimerState::Expired);
+                    if (testCallback != nullptr) {
+                        testCallback(false);
+                    }
+                });
         mTouchTimer->start();
     }