Merge "InputVerifier: use named parameters to format! where possible" into main
diff --git a/include/input/DisplayTopologyGraph.h b/include/input/DisplayTopologyGraph.h
index 90427bd..f3f5148 100644
--- a/include/input/DisplayTopologyGraph.h
+++ b/include/input/DisplayTopologyGraph.h
@@ -43,7 +43,7 @@
 struct DisplayTopologyAdjacentDisplay {
     ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
     DisplayTopologyPosition position;
-    float offsetPx;
+    float offsetDp;
 };
 
 /**
@@ -52,6 +52,7 @@
 struct DisplayTopologyGraph {
     ui::LogicalDisplayId primaryDisplayId = ui::LogicalDisplayId::INVALID;
     std::unordered_map<ui::LogicalDisplayId, std::vector<DisplayTopologyAdjacentDisplay>> graph;
+    std::unordered_map<ui::LogicalDisplayId, int> displaysDensity;
 };
 
 } // namespace android
diff --git a/include/input/Input.h b/include/input/Input.h
index 2cabd56..e84023e 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -92,11 +92,23 @@
             static_cast<int32_t>(android::os::MotionEventFlag::NO_FOCUS_CHANGE),
 
     /**
-     * This event was generated or modified by accessibility service.
+     * This event was injected from some AccessibilityService, which may be either an
+     * Accessibility Tool OR a service using that API for purposes other than assisting users
+     * with disabilities.
      */
     AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT =
             static_cast<int32_t>(android::os::MotionEventFlag::IS_ACCESSIBILITY_EVENT),
 
+    /**
+     * This event was injected from an AccessibilityService with the
+     * AccessibilityServiceInfo#isAccessibilityTool property set to true. These services (known as
+     * "Accessibility Tools") are used to assist users with disabilities, so events from these
+     * services should be able to reach all Views including Views which set
+     * View#isAccessibilityDataSensitive to true.
+     */
+    AMOTION_EVENT_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL =
+            static_cast<int32_t>(android::os::MotionEventFlag::INJECTED_FROM_ACCESSIBILITY_TOOL),
+
     AMOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS =
             static_cast<int32_t>(android::os::MotionEventFlag::TARGET_ACCESSIBILITY_FOCUS),
 
@@ -347,6 +359,9 @@
     POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY =
             android::os::IInputConstants::POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY,
 
+    POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL =
+            android::os::IInputConstants::POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL,
+
     /* These flags are set by the input dispatcher. */
 
     // Indicates that the input event was injected.
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index 0a22588..bc7ae37 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -38,6 +38,7 @@
 #endif
 
 #include "BuildFlags.h"
+#include "Constants.h"
 #include "OS.h"
 #include "RpcState.h"
 
@@ -70,8 +71,6 @@
 constexpr bool kEnableRecording = false;
 #endif
 
-// Log any reply transactions for which the data exceeds this size
-#define LOG_REPLIES_OVER_SIZE (300 * 1024)
 // ---------------------------------------------------------------------------
 
 IBinder::IBinder()
@@ -412,7 +411,7 @@
     // In case this is being transacted on in the same process.
     if (reply != nullptr) {
         reply->setDataPosition(0);
-        if (reply->dataSize() > LOG_REPLIES_OVER_SIZE) {
+        if (reply->dataSize() > binder::kLogTransactionsOverBytes) {
             ALOGW("Large reply transaction of %zu bytes, interface descriptor %s, code %d",
                   reply->dataSize(), String8(getInterfaceDescriptor()).c_str(), code);
         }
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 444f061..c13e0f9 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -28,6 +28,7 @@
 #include <stdio.h>
 
 #include "BuildFlags.h"
+#include "Constants.h"
 #include "file.h"
 
 //#undef ALOGV
@@ -63,9 +64,6 @@
 
 static constexpr uint32_t kBinderProxyCountWarnInterval = 5000;
 
-// Log any transactions for which the data exceeds this size
-#define LOG_TRANSACTIONS_OVER_SIZE (300 * 1024)
-
 enum {
     LIMIT_REACHED_MASK = 0x80000000,        // A flag denoting that the limit has been reached
     WARNING_REACHED_MASK = 0x40000000,      // A flag denoting that the warning has been reached
@@ -403,9 +401,11 @@
 
             status = IPCThreadState::self()->transact(binderHandle(), code, data, reply, flags);
         }
-        if (data.dataSize() > LOG_TRANSACTIONS_OVER_SIZE) {
+
+        if (data.dataSize() > binder::kLogTransactionsOverBytes) {
             RpcMutexUniqueLock _l(mLock);
-            ALOGW("Large outgoing transaction of %zu bytes, interface descriptor %s, code %d",
+            ALOGW("Large outgoing transaction of %zu bytes, interface descriptor %s, code %d was "
+                  "sent",
                   data.dataSize(), String8(mDescriptorCache).c_str(), code);
         }
 
diff --git a/libs/binder/Constants.h b/libs/binder/Constants.h
new file mode 100644
index 0000000..b75493c
--- /dev/null
+++ b/libs/binder/Constants.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 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
+
+namespace android::binder {
+
+/**
+ * See also BINDER_VM_SIZE. In kernel binder, the sum of all transactions must be allocated in this
+ * space. Large transactions are very error prone. In general, we should work to reduce this limit.
+ * The same limit is used in RPC binder for consistency.
+ */
+constexpr size_t kLogTransactionsOverBytes = 300 * 1024;
+
+/**
+ * See b/392575419 - this limit is chosen for a specific usecase, because RPC binder does not have
+ * support for shared memory in the Android Baklava timeframe. This was 100 KB during and before
+ * Android V.
+ *
+ * Keeping this low helps preserve overall system performance. Transactions of this size are far too
+ * expensive to make multiple copies over binder or sockets, and they should be avoided if at all
+ * possible and transition to shared memory.
+ */
+constexpr size_t kRpcTransactionLimitBytes = 600 * 1024;
+
+} // namespace android::binder
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index cdc53ff..1c1b6f3 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -38,6 +38,10 @@
 #include "Utils.h"
 #include "binder_module.h"
 
+#if (defined(__ANDROID__) || defined(__Fuchsia__)) && !defined(BINDER_WITH_KERNEL_IPC)
+#error Android and Fuchsia are expected to have BINDER_WITH_KERNEL_IPC
+#endif
+
 #if LOG_NDEBUG
 
 #define IF_LOG_TRANSACTIONS() if (false)
@@ -1229,7 +1233,7 @@
             std::string message = logStream.str();
             ALOGI("%s", message.c_str());
         }
-#if defined(__ANDROID__)
+#if defined(BINDER_WITH_KERNEL_IPC)
         if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
             err = NO_ERROR;
         else
@@ -1625,7 +1629,7 @@
         IPCThreadState* const self = static_cast<IPCThreadState*>(st);
         if (self) {
                 self->flushCommands();
-#if defined(__ANDROID__)
+#if defined(BINDER_WITH_KERNEL_IPC)
         if (self->mProcess->mDriverFD >= 0) {
             ioctl(self->mProcess->mDriverFD, BINDER_THREAD_EXIT, 0);
         }
@@ -1641,7 +1645,7 @@
     binder_frozen_status_info info = {};
     info.pid = pid;
 
-#if defined(__ANDROID__)
+#if defined(BINDER_WITH_KERNEL_IPC)
     if (ioctl(self()->mProcess->mDriverFD, BINDER_GET_FROZEN_INFO, &info) < 0)
         ret = -errno;
 #endif
@@ -1660,7 +1664,7 @@
     info.timeout_ms = timeout_ms;
 
 
-#if defined(__ANDROID__)
+#if defined(BINDER_WITH_KERNEL_IPC)
     if (ioctl(self()->mProcess->mDriverFD, BINDER_FREEZE, &info) < 0)
         ret = -errno;
 #endif
@@ -1678,7 +1682,7 @@
     if (!ProcessState::isDriverFeatureEnabled(ProcessState::DriverFeature::EXTENDED_ERROR))
         return;
 
-#if defined(__ANDROID__)
+#if defined(BINDER_WITH_KERNEL_IPC)
     if (ioctl(self()->mProcess->mDriverFD, BINDER_GET_EXTENDED_ERROR, &ee) < 0) {
         ALOGE("Failed to get extended error: %s", strerror(errno));
         return;
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 719e445..c9ca646 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -43,7 +43,11 @@
 #include <binder/IPermissionController.h>
 #endif
 
-#ifdef __ANDROID__
+#if !(defined(__ANDROID__) || defined(__FUCHSIA))
+#define BINDER_SERVICEMANAGEMENT_DELEGATION_SUPPORT
+#endif
+
+#if !defined(BINDER_SERVICEMANAGEMENT_DELEGATION_SUPPORT)
 #include <cutils/properties.h>
 #else
 #include "ServiceManagerHost.h"
@@ -902,7 +906,7 @@
     return ret;
 }
 
-#ifndef __ANDROID__
+#if defined(BINDER_SERVICEMANAGEMENT_DELEGATION_SUPPORT)
 // CppBackendShim for host. Implements the old libbinder android::IServiceManager API.
 // The internal implementation of the AIDL interface android::os::IServiceManager calls into
 // on-device service manager.
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index bc027d7..777c22a 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -299,8 +299,13 @@
             obj.handle = handle;
             obj.cookie = 0;
         } else {
+#if __linux__
             int policy = local->getMinSchedulerPolicy();
             int priority = local->getMinSchedulerPriority();
+#else
+            int policy = 0;
+            int priority = 0;
+#endif
 
             if (policy != 0 || priority != 0) {
                 // override value, since it is set explicitly
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index 0e1e9b4..0bec379 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -48,6 +48,10 @@
 #define DEFAULT_MAX_BINDER_THREADS 15
 #define DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION 1
 
+#if defined(__ANDROID__) || defined(__Fuchsia__)
+#define EXPECT_BINDER_OPEN_SUCCESS
+#endif
+
 #ifdef __ANDROID_VNDK__
 const char* kDefaultDriver = "/dev/vndbinder";
 #else
@@ -613,7 +617,7 @@
         }
     }
 
-#ifdef __ANDROID__
+#if defined(EXPECT_BINDER_OPEN_SUCCESS)
     LOG_ALWAYS_FATAL_IF(!opened.ok(),
                         "Binder driver '%s' could not be opened. Error: %s. Terminating.",
                         driver, error.c_str());
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index fe6e1a3..03d974d 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -23,6 +23,7 @@
 #include <binder/IPCThreadState.h>
 #include <binder/RpcServer.h>
 
+#include "Constants.h"
 #include "Debug.h"
 #include "RpcWireFormat.h"
 #include "Utils.h"
@@ -337,6 +338,8 @@
 }
 
 RpcState::CommandData::CommandData(size_t size) : mSize(size) {
+    if (size == 0) return;
+
     // The maximum size for regular binder is 1MB for all concurrent
     // transactions. A very small proportion of transactions are even
     // larger than a page, but we need to avoid allocating too much
@@ -348,11 +351,11 @@
     // transaction (in some cases, additional fixed size amounts are added),
     // though for rough consistency, we should avoid cases where this data type
     // is used for multiple dynamic allocations for a single transaction.
-    constexpr size_t kMaxTransactionAllocation = 100 * 1000;
-    if (size == 0) return;
-    if (size > kMaxTransactionAllocation) {
-        ALOGW("Transaction requested too much data allocation %zu", size);
+    if (size > binder::kRpcTransactionLimitBytes) {
+        ALOGE("Transaction requested too much data allocation: %zu bytes, failing.", size);
         return;
+    } else if (size > binder::kLogTransactionsOverBytes) {
+        ALOGW("Transaction too large: inefficient and in danger of breaking: %zu bytes.", size);
     }
     mData.reset(new (std::nothrow) uint8_t[size]);
 }
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index 8404a48..adef9ea 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -16,6 +16,7 @@
         "libdowncast_rs",
         "liblibc",
         "liblog_rust",
+        "libzerocopy",
     ],
     host_supported: true,
     vendor_available: true,
@@ -205,6 +206,7 @@
         "libdowncast_rs",
         "liblibc",
         "liblog_rust",
+        "libzerocopy",
     ],
 }
 
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 77b80fe..0026f21 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -116,7 +116,7 @@
 pub use error::{ExceptionCode, IntoBinderResult, Status, StatusCode};
 pub use parcel::{ParcelFileDescriptor, Parcelable, ParcelableHolder};
 #[cfg(not(trusty))]
-pub use persistable_bundle::PersistableBundle;
+pub use persistable_bundle::{PersistableBundle, ValueType};
 pub use proxy::{DeathRecipient, SpIBinder, WpIBinder};
 #[cfg(not(any(trusty, android_ndk)))]
 pub use service::{
diff --git a/libs/binder/rust/src/persistable_bundle.rs b/libs/binder/rust/src/persistable_bundle.rs
index d71ed73..8639c0d 100644
--- a/libs/binder/rust/src/persistable_bundle.rs
+++ b/libs/binder/rust/src/persistable_bundle.rs
@@ -22,19 +22,28 @@
 };
 use binder_ndk_sys::{
     APersistableBundle, APersistableBundle_delete, APersistableBundle_dup,
-    APersistableBundle_erase, APersistableBundle_getBoolean, APersistableBundle_getBooleanVector,
-    APersistableBundle_getDouble, APersistableBundle_getDoubleVector, APersistableBundle_getInt,
-    APersistableBundle_getIntVector, APersistableBundle_getLong, APersistableBundle_getLongVector,
-    APersistableBundle_getPersistableBundle, APersistableBundle_isEqual, APersistableBundle_new,
+    APersistableBundle_erase, APersistableBundle_getBoolean, APersistableBundle_getBooleanKeys,
+    APersistableBundle_getBooleanVector, APersistableBundle_getBooleanVectorKeys,
+    APersistableBundle_getDouble, APersistableBundle_getDoubleKeys,
+    APersistableBundle_getDoubleVector, APersistableBundle_getDoubleVectorKeys,
+    APersistableBundle_getInt, APersistableBundle_getIntKeys, APersistableBundle_getIntVector,
+    APersistableBundle_getIntVectorKeys, APersistableBundle_getLong,
+    APersistableBundle_getLongKeys, APersistableBundle_getLongVector,
+    APersistableBundle_getLongVectorKeys, APersistableBundle_getPersistableBundle,
+    APersistableBundle_getPersistableBundleKeys, APersistableBundle_getString,
+    APersistableBundle_getStringKeys, APersistableBundle_getStringVector,
+    APersistableBundle_getStringVectorKeys, APersistableBundle_isEqual, APersistableBundle_new,
     APersistableBundle_putBoolean, APersistableBundle_putBooleanVector,
     APersistableBundle_putDouble, APersistableBundle_putDoubleVector, APersistableBundle_putInt,
     APersistableBundle_putIntVector, APersistableBundle_putLong, APersistableBundle_putLongVector,
     APersistableBundle_putPersistableBundle, APersistableBundle_putString,
     APersistableBundle_putStringVector, APersistableBundle_readFromParcel, APersistableBundle_size,
-    APersistableBundle_writeToParcel, APERSISTABLEBUNDLE_KEY_NOT_FOUND,
+    APersistableBundle_writeToParcel, APERSISTABLEBUNDLE_ALLOCATOR_FAILED,
+    APERSISTABLEBUNDLE_KEY_NOT_FOUND,
 };
-use std::ffi::{c_char, CString, NulError};
-use std::ptr::{null_mut, NonNull};
+use std::ffi::{c_char, c_void, CStr, CString, NulError};
+use std::ptr::{null_mut, slice_from_raw_parts_mut, NonNull};
+use zerocopy::FromZeros;
 
 /// A mapping from string keys to values of various types.
 #[derive(Debug)]
@@ -374,6 +383,53 @@
         }
     }
 
+    /// Gets the string value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_string(&self, key: &str) -> Result<Option<String>, NulError> {
+        let key = CString::new(key)?;
+        let mut value = null_mut();
+        let mut allocated_size: usize = 0;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the lifetime of `key`. The value pointer must be valid because it comes
+        // from a reference.
+        let value_size_bytes = unsafe {
+            APersistableBundle_getString(
+                self.0.as_ptr(),
+                key.as_ptr(),
+                &mut value,
+                Some(string_allocator),
+                (&raw mut allocated_size).cast(),
+            )
+        };
+        match value_size_bytes {
+            APERSISTABLEBUNDLE_KEY_NOT_FOUND => Ok(None),
+            APERSISTABLEBUNDLE_ALLOCATOR_FAILED => {
+                panic!("APersistableBundle_getString failed to allocate string");
+            }
+            _ => {
+                let raw_slice = slice_from_raw_parts_mut(value.cast(), allocated_size);
+                // SAFETY: The pointer was returned from string_allocator, which used
+                // `Box::into_raw`, and we've got the appropriate size back from allocated_size.
+                let boxed_slice: Box<[u8]> = unsafe { Box::from_raw(raw_slice) };
+                assert_eq!(
+                    allocated_size,
+                    usize::try_from(value_size_bytes)
+                        .expect("APersistableBundle_getString returned negative value size")
+                        + 1
+                );
+                let c_string = CString::from_vec_with_nul(boxed_slice.into())
+                    .expect("APersistableBundle_getString returned string missing NUL byte");
+                let string = c_string
+                    .into_string()
+                    .expect("APersistableBundle_getString returned invalid UTF-8");
+                Ok(Some(string))
+            }
+        }
+    }
+
     /// Gets the vector of `T` associated with the given key.
     ///
     /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
@@ -388,9 +444,10 @@
     /// call. It must allow a null pointer for the buffer, and must return the size in bytes of
     /// buffer it requires. If it is given a non-null buffer pointer it must write that number of
     /// bytes to the buffer, which must be a whole number of valid `T` values.
-    unsafe fn get_vec<T: Clone + Default>(
+    unsafe fn get_vec<T: Clone>(
         &self,
         key: &str,
+        default: T,
         get_func: unsafe extern "C" fn(
             *const APersistableBundle,
             *const c_char,
@@ -404,9 +461,12 @@
         // to be valid for the lifetime of `key`. A null pointer is allowed for the buffer.
         match unsafe { get_func(self.0.as_ptr(), key.as_ptr(), null_mut(), 0) } {
             APERSISTABLEBUNDLE_KEY_NOT_FOUND => Ok(None),
+            APERSISTABLEBUNDLE_ALLOCATOR_FAILED => {
+                panic!("APersistableBundle_getStringVector failed to allocate string");
+            }
             required_buffer_size => {
                 let mut value = vec![
-                    T::default();
+                    default;
                     usize::try_from(required_buffer_size).expect(
                         "APersistableBundle_get*Vector returned invalid size"
                     ) / size_of::<T>()
@@ -426,6 +486,9 @@
                     APERSISTABLEBUNDLE_KEY_NOT_FOUND => {
                         panic!("APersistableBundle_get*Vector failed to find key after first finding it");
                     }
+                    APERSISTABLEBUNDLE_ALLOCATOR_FAILED => {
+                        panic!("APersistableBundle_getStringVector failed to allocate string");
+                    }
                     _ => Ok(Some(value)),
                 }
             }
@@ -439,7 +502,7 @@
     pub fn get_bool_vec(&self, key: &str) -> Result<Option<Vec<bool>>, NulError> {
         // SAFETY: APersistableBundle_getBooleanVector fulfils all the safety requirements of
         // `get_vec`.
-        unsafe { self.get_vec(key, APersistableBundle_getBooleanVector) }
+        unsafe { self.get_vec(key, Default::default(), APersistableBundle_getBooleanVector) }
     }
 
     /// Gets the i32 vector value associated with the given key.
@@ -449,7 +512,7 @@
     pub fn get_int_vec(&self, key: &str) -> Result<Option<Vec<i32>>, NulError> {
         // SAFETY: APersistableBundle_getIntVector fulfils all the safety requirements of
         // `get_vec`.
-        unsafe { self.get_vec(key, APersistableBundle_getIntVector) }
+        unsafe { self.get_vec(key, Default::default(), APersistableBundle_getIntVector) }
     }
 
     /// Gets the i64 vector value associated with the given key.
@@ -459,7 +522,7 @@
     pub fn get_long_vec(&self, key: &str) -> Result<Option<Vec<i64>>, NulError> {
         // SAFETY: APersistableBundle_getLongVector fulfils all the safety requirements of
         // `get_vec`.
-        unsafe { self.get_vec(key, APersistableBundle_getLongVector) }
+        unsafe { self.get_vec(key, Default::default(), APersistableBundle_getLongVector) }
     }
 
     /// Gets the f64 vector value associated with the given key.
@@ -469,7 +532,45 @@
     pub fn get_double_vec(&self, key: &str) -> Result<Option<Vec<f64>>, NulError> {
         // SAFETY: APersistableBundle_getDoubleVector fulfils all the safety requirements of
         // `get_vec`.
-        unsafe { self.get_vec(key, APersistableBundle_getDoubleVector) }
+        unsafe { self.get_vec(key, Default::default(), APersistableBundle_getDoubleVector) }
+    }
+
+    /// Gets the string vector value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_string_vec(&self, key: &str) -> Result<Option<Vec<String>>, NulError> {
+        if let Some(value) =
+            // SAFETY: `get_string_vector_with_allocator` fulfils all the safety requirements of
+            // `get_vec`.
+            unsafe { self.get_vec(key, null_mut(), get_string_vector_with_allocator) }?
+        {
+            Ok(Some(
+                value
+                    .into_iter()
+                    .map(|s| {
+                        // SAFETY: The pointer was returned from `string_allocator`, which used
+                        // `Box::into_raw`, and `APersistableBundle_getStringVector` should have
+                        // written valid bytes to it including a NUL terminator in the last
+                        // position.
+                        let string_length = unsafe { CStr::from_ptr(s) }.count_bytes();
+                        let raw_slice = slice_from_raw_parts_mut(s.cast(), string_length + 1);
+                        // SAFETY: The pointer was returned from `string_allocator`, which used
+                        // `Box::into_raw`, and we've got the appropriate size back by checking the
+                        // length of the string.
+                        let boxed_slice: Box<[u8]> = unsafe { Box::from_raw(raw_slice) };
+                        let c_string = CString::from_vec_with_nul(boxed_slice.into()).expect(
+                            "APersistableBundle_getStringVector returned string missing NUL byte",
+                        );
+                        c_string
+                            .into_string()
+                            .expect("APersistableBundle_getStringVector returned invalid UTF-8")
+                    })
+                    .collect(),
+            ))
+        } else {
+            Ok(None)
+        }
     }
 
     /// Gets the `PersistableBundle` value associated with the given key.
@@ -493,6 +594,201 @@
             Ok(None)
         }
     }
+
+    /// Calls the appropriate `APersistableBundle_get*Keys` function for the given `value_type`,
+    /// with our `string_allocator` and a null context pointer.
+    ///
+    /// # Safety
+    ///
+    /// `out_keys` must either be null or point to a buffer of at least `buffer_size_bytes` bytes,
+    /// properly aligned for `T`, and not otherwise accessed for the duration of the call.
+    unsafe fn get_keys_raw(
+        &self,
+        value_type: ValueType,
+        out_keys: *mut *mut c_char,
+        buffer_size_bytes: i32,
+    ) -> i32 {
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. Our caller guarantees an appropriate value for
+        // `out_keys` and `buffer_size_bytes`.
+        unsafe {
+            match value_type {
+                ValueType::Boolean => APersistableBundle_getBooleanKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::Integer => APersistableBundle_getIntKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::Long => APersistableBundle_getLongKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::Double => APersistableBundle_getDoubleKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::String => APersistableBundle_getStringKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::BooleanVector => APersistableBundle_getBooleanVectorKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::IntegerVector => APersistableBundle_getIntVectorKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::LongVector => APersistableBundle_getLongVectorKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::DoubleVector => APersistableBundle_getDoubleVectorKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::StringVector => APersistableBundle_getStringVectorKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::PersistableBundle => APersistableBundle_getPersistableBundleKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+            }
+        }
+    }
+
+    /// Gets all the keys associated with values of the given type.
+    pub fn keys_for_type(&self, value_type: ValueType) -> Vec<String> {
+        // SAFETY: A null pointer is allowed for the buffer.
+        match unsafe { self.get_keys_raw(value_type, null_mut(), 0) } {
+            APERSISTABLEBUNDLE_ALLOCATOR_FAILED => {
+                panic!("APersistableBundle_get*Keys failed to allocate string");
+            }
+            required_buffer_size => {
+                let required_buffer_size_usize = usize::try_from(required_buffer_size)
+                    .expect("APersistableBundle_get*Keys returned invalid size");
+                assert_eq!(required_buffer_size_usize % size_of::<*mut c_char>(), 0);
+                let mut keys =
+                    vec![null_mut(); required_buffer_size_usize / size_of::<*mut c_char>()];
+                // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for
+                // the lifetime of the `PersistableBundle`. The keys buffer pointer is valid as it
+                // comes from the Vec we just allocated.
+                if unsafe { self.get_keys_raw(value_type, keys.as_mut_ptr(), required_buffer_size) }
+                    == APERSISTABLEBUNDLE_ALLOCATOR_FAILED
+                {
+                    panic!("APersistableBundle_get*Keys failed to allocate string");
+                }
+                keys.into_iter()
+                    .map(|key| {
+                        // SAFETY: The pointer was returned from `string_allocator`, which used
+                        // `Box::into_raw`, and `APersistableBundle_getStringVector` should have
+                        // written valid bytes to it including a NUL terminator in the last
+                        // position.
+                        let string_length = unsafe { CStr::from_ptr(key) }.count_bytes();
+                        let raw_slice = slice_from_raw_parts_mut(key.cast(), string_length + 1);
+                        // SAFETY: The pointer was returned from `string_allocator`, which used
+                        // `Box::into_raw`, and we've got the appropriate size back by checking the
+                        // length of the string.
+                        let boxed_slice: Box<[u8]> = unsafe { Box::from_raw(raw_slice) };
+                        let c_string = CString::from_vec_with_nul(boxed_slice.into())
+                            .expect("APersistableBundle_get*Keys returned string missing NUL byte");
+                        c_string
+                            .into_string()
+                            .expect("APersistableBundle_get*Keys returned invalid UTF-8")
+                    })
+                    .collect()
+            }
+        }
+    }
+
+    /// Returns an iterator over all keys in the bundle, along with the type of their associated
+    /// value.
+    pub fn keys(&self) -> impl Iterator<Item = (String, ValueType)> + use<'_> {
+        [
+            ValueType::Boolean,
+            ValueType::Integer,
+            ValueType::Long,
+            ValueType::Double,
+            ValueType::String,
+            ValueType::BooleanVector,
+            ValueType::IntegerVector,
+            ValueType::LongVector,
+            ValueType::DoubleVector,
+            ValueType::StringVector,
+            ValueType::PersistableBundle,
+        ]
+        .iter()
+        .flat_map(|value_type| {
+            self.keys_for_type(*value_type).into_iter().map(|key| (key, *value_type))
+        })
+    }
+}
+
+/// Wrapper around `APersistableBundle_getStringVector` to pass `string_allocator` and a null
+/// context pointer.
+///
+/// # Safety
+///
+/// * `bundle` must point to a valid `APersistableBundle` which is not modified for the duration of
+///   the call.
+/// * `key` must point to a valid NUL-terminated C string.
+/// * `buffer` must either be null or point to a buffer of at least `buffer_size_bytes` bytes,
+///   properly aligned for `T`, and not otherwise accessed for the duration of the call.
+unsafe extern "C" fn get_string_vector_with_allocator(
+    bundle: *const APersistableBundle,
+    key: *const c_char,
+    buffer: *mut *mut c_char,
+    buffer_size_bytes: i32,
+) -> i32 {
+    // SAFETY: The safety requirements are all guaranteed by our caller according to the safety
+    // documentation above.
+    unsafe {
+        APersistableBundle_getStringVector(
+            bundle,
+            key,
+            buffer,
+            buffer_size_bytes,
+            Some(string_allocator),
+            null_mut(),
+        )
+    }
 }
 
 // SAFETY: The underlying *APersistableBundle can be moved between threads.
@@ -558,9 +854,59 @@
     }
 }
 
+/// Allocates a boxed slice of the given size in bytes, returns a pointer to it and writes its size
+/// to `*context`.
+///
+/// # Safety
+///
+/// `context` must either be null or point to a `usize` to which we can write.
+unsafe extern "C" fn string_allocator(size: i32, context: *mut c_void) -> *mut c_char {
+    let Ok(size) = size.try_into() else {
+        return null_mut();
+    };
+    let Ok(boxed_slice) = <[c_char]>::new_box_zeroed_with_elems(size) else {
+        return null_mut();
+    };
+    if !context.is_null() {
+        // SAFETY: The caller promised that `context` is either null or points to a `usize` to which
+        // we can write, and we just checked that it's not null.
+        unsafe {
+            *context.cast::<usize>() = size;
+        }
+    }
+    Box::into_raw(boxed_slice).cast()
+}
+
 impl_deserialize_for_unstructured_parcelable!(PersistableBundle);
 impl_serialize_for_unstructured_parcelable!(PersistableBundle);
 
+/// The types which may be stored as values in a [`PersistableBundle`].
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum ValueType {
+    /// A `bool`.
+    Boolean,
+    /// An `i32`.
+    Integer,
+    /// An `i64`.
+    Long,
+    /// An `f64`.
+    Double,
+    /// A string.
+    String,
+    /// A vector of `bool`s.
+    BooleanVector,
+    /// A vector of `i32`s.
+    IntegerVector,
+    /// A vector of `i64`s.
+    LongVector,
+    /// A vector of `f64`s.
+    DoubleVector,
+    /// A vector of strings.
+    StringVector,
+    /// A nested `PersistableBundle`.
+    PersistableBundle,
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
@@ -589,6 +935,7 @@
         assert_eq!(bundle.get_int_vec("foo"), Ok(None));
         assert_eq!(bundle.get_long_vec("foo"), Ok(None));
         assert_eq!(bundle.get_double_vec("foo"), Ok(None));
+        assert_eq!(bundle.get_string("foo"), Ok(None));
     }
 
     #[test]
@@ -639,10 +986,15 @@
     }
 
     #[test]
-    fn insert_string() {
+    fn insert_get_string() {
         let mut bundle = PersistableBundle::new();
+
         assert_eq!(bundle.insert_string("string", "foo"), Ok(()));
-        assert_eq!(bundle.size(), 1);
+        assert_eq!(bundle.insert_string("empty", ""), Ok(()));
+        assert_eq!(bundle.size(), 2);
+
+        assert_eq!(bundle.get_string("string"), Ok(Some("foo".to_string())));
+        assert_eq!(bundle.get_string("empty"), Ok(Some("".to_string())));
     }
 
     #[test]
@@ -675,6 +1027,10 @@
         assert_eq!(bundle.get_int_vec("int"), Ok(Some(vec![42])));
         assert_eq!(bundle.get_long_vec("long"), Ok(Some(vec![66, 67, 68])));
         assert_eq!(bundle.get_double_vec("double"), Ok(Some(vec![123.4])));
+        assert_eq!(
+            bundle.get_string_vec("string"),
+            Ok(Some(vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]))
+        );
     }
 
     #[test]
@@ -688,4 +1044,33 @@
 
         assert_eq!(bundle.get_persistable_bundle("bundle"), Ok(Some(sub_bundle)));
     }
+
+    #[test]
+    fn get_keys() {
+        let mut bundle = PersistableBundle::new();
+
+        assert_eq!(bundle.keys_for_type(ValueType::Boolean), Vec::<String>::new());
+        assert_eq!(bundle.keys_for_type(ValueType::Integer), Vec::<String>::new());
+        assert_eq!(bundle.keys_for_type(ValueType::StringVector), Vec::<String>::new());
+
+        assert_eq!(bundle.insert_bool("bool1", false), Ok(()));
+        assert_eq!(bundle.insert_bool("bool2", true), Ok(()));
+        assert_eq!(bundle.insert_int("int", 42), Ok(()));
+
+        assert_eq!(
+            bundle.keys_for_type(ValueType::Boolean),
+            vec!["bool1".to_string(), "bool2".to_string()]
+        );
+        assert_eq!(bundle.keys_for_type(ValueType::Integer), vec!["int".to_string()]);
+        assert_eq!(bundle.keys_for_type(ValueType::StringVector), Vec::<String>::new());
+
+        assert_eq!(
+            bundle.keys().collect::<Vec<_>>(),
+            vec![
+                ("bool1".to_string(), ValueType::Boolean),
+                ("bool2".to_string(), ValueType::Boolean),
+                ("int".to_string(), ValueType::Integer),
+            ]
+        );
+    }
 }
diff --git a/libs/binder/tests/IBinderRpcTest.aidl b/libs/binder/tests/IBinderRpcTest.aidl
index 1164767..dcd6461 100644
--- a/libs/binder/tests/IBinderRpcTest.aidl
+++ b/libs/binder/tests/IBinderRpcTest.aidl
@@ -34,6 +34,8 @@
     void holdBinder(@nullable IBinder binder);
     @nullable IBinder getHeldBinder();
 
+    byte[] repeatBytes(in byte[] bytes);
+
     // Idea is client creates its own instance of IBinderRpcTest and calls this,
     // and the server calls 'binder' with (calls - 1) passing itself as 'binder',
     // going back and forth until calls = 0
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 9f656ec..e88e3f3 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -711,6 +711,35 @@
     proc.proc->sessions.erase(proc.proc->sessions.begin() + 1);
 }
 
+// TODO(b/392717039): can we move this to universal tests?
+TEST_P(BinderRpc, SendTooLargeVector) {
+    if (GetParam().singleThreaded) {
+        GTEST_SKIP() << "Requires multi-threaded server to test one of the sessions crashing.";
+    }
+
+    auto proc = createRpcTestSocketServerProcess({.numSessions = 2});
+
+    // need a working transaction
+    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+
+    // see libbinder internal Constants.h
+    const size_t kTooLargeSize = 650 * 1024;
+    const std::vector<uint8_t> kTestValue(kTooLargeSize / sizeof(uint8_t), 42);
+
+    // TODO(b/392717039): Telling a server to allocate too much data currently causes the session to
+    // close since RpcServer treats any transaction error as a failure. We likely want to change
+    // this behavior to be a soft failure, since it isn't hard to keep track of this state.
+    sp<IBinderRpcTest> rootIface2 = interface_cast<IBinderRpcTest>(proc.proc->sessions.at(1).root);
+    std::vector<uint8_t> result;
+    status_t res = rootIface2->repeatBytes(kTestValue, &result).transactionError();
+
+    // TODO(b/392717039): consistent error results always
+    EXPECT_TRUE(res == -ECONNRESET || res == DEAD_OBJECT) << statusToString(res);
+
+    // died, so remove it for checks in destructor of proc
+    proc.proc->sessions.erase(proc.proc->sessions.begin() + 1);
+}
+
 TEST_P(BinderRpc, SessionWithIncomingThreadpoolDoesntLeak) {
     if (clientOrServerSingleThreaded()) {
         GTEST_SKIP() << "This test requires multiple threads";
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index dc22647..6e00246 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -348,6 +348,10 @@
         *out = binder;
         return Status::ok();
     }
+    Status repeatBytes(const std::vector<uint8_t>& bytes, std::vector<uint8_t>* out) override {
+        *out = bytes;
+        return Status::ok();
+    }
     static sp<IBinder> mHeldBinder;
     Status holdBinder(const sp<IBinder>& binder) override {
         mHeldBinder = binder;
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index 4b9dcf8..d227e6e 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -209,6 +209,18 @@
     EXPECT_EQ(0, MyBinderRpcSession::gNum);
 }
 
+TEST_P(BinderRpc, SendLargeVector) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    // see libbinder internal Constants.h
+    const size_t kLargeSize = 550 * 1024;
+    const std::vector<uint8_t> kTestValue(kLargeSize / sizeof(uint8_t), 42);
+
+    std::vector<uint8_t> result;
+    EXPECT_OK(proc.rootIface->repeatBytes(kTestValue, &result));
+    EXPECT_EQ(result, kTestValue);
+}
+
 TEST_P(BinderRpc, RepeatTheirBinder) {
     auto proc = createRpcTestSocketServerProcess({});
 
diff --git a/libs/binder/trusty/binderRpcTest/manifest.json b/libs/binder/trusty/binderRpcTest/manifest.json
index 6e20b8a..da0f2ed 100644
--- a/libs/binder/trusty/binderRpcTest/manifest.json
+++ b/libs/binder/trusty/binderRpcTest/manifest.json
@@ -1,6 +1,6 @@
 {
     "uuid": "9dbe9fb8-60fd-4bdd-af86-03e95d7ad78b",
     "app_name": "binderRpcTest",
-    "min_heap": 262144,
+    "min_heap": 4194304,
     "min_stack": 20480
 }
diff --git a/libs/binder/trusty/binderRpcTest/service/manifest.json b/libs/binder/trusty/binderRpcTest/service/manifest.json
index d2a1fc0..55ff49c 100644
--- a/libs/binder/trusty/binderRpcTest/service/manifest.json
+++ b/libs/binder/trusty/binderRpcTest/service/manifest.json
@@ -1,7 +1,7 @@
 {
     "uuid": "87e424e5-69d7-4bbd-8b7c-7e24812cbc94",
     "app_name": "binderRpcTestService",
-    "min_heap": 65536,
+    "min_heap": 4194304,
     "min_stack": 20480,
     "mgmt_flags": {
         "restart_on_exit": true,
diff --git a/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs b/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs
index 22cba44..caf3117 100644
--- a/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs
+++ b/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs
@@ -82,6 +82,9 @@
     fn repeatBinder(&self, _binder: Option<&SpIBinder>) -> Result<Option<SpIBinder>, Status> {
         todo!()
     }
+    fn repeatBytes(&self, _bytes: &[u8]) -> Result<Vec<u8>, Status> {
+        todo!()
+    }
     fn holdBinder(&self, _binder: Option<&SpIBinder>) -> Result<(), Status> {
         todo!()
     }
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/main.rs b/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
index c4a758a..6f454be 100644
--- a/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
@@ -96,6 +96,9 @@
             None => Err(Status::from(StatusCode::BAD_VALUE)),
         }
     }
+    fn repeatBytes(&self, _bytes: &[u8]) -> Result<Vec<u8>, Status> {
+        todo!()
+    }
     fn holdBinder(&self, binder: Option<&SpIBinder>) -> Result<(), Status> {
         *HOLD_BINDER.lock().unwrap() = binder.cloned();
         Ok(())
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index f012586..270bfbd 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -477,9 +477,14 @@
     return NO_ERROR;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
+                                            const sp<Fence>& releaseFence) {
+#else
 status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
         const sp<Fence>& releaseFence, EGLDisplay eglDisplay,
         EGLSyncKHR eglFence) {
+#endif
     ATRACE_CALL();
     ATRACE_BUFFER_INDEX(slot);
 
@@ -493,27 +498,6 @@
         return BAD_VALUE;
     }
 
-#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
-    if (eglFence != EGL_NO_SYNC_KHR) {
-        // Most platforms will be using native fences, so it's unlikely that we'll ever have to
-        // process an eglFence. Ideally we can remove this code eventually. In the mean time, do our
-        // best to wait for it so the buffer stays valid, otherwise return an error to the caller.
-        //
-        // EGL_SYNC_FLUSH_COMMANDS_BIT_KHR so that we don't wait forever on a fence that hasn't
-        // shown up on the GPU yet.
-        EGLint result = eglClientWaitSyncKHR(eglDisplay, eglFence, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
-                                             1000000000);
-        if (result == EGL_FALSE) {
-            BQ_LOGE("releaseBuffer: error %#x waiting for fence", eglGetError());
-            return UNKNOWN_ERROR;
-        } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
-            BQ_LOGE("releaseBuffer: timeout waiting for fence");
-            return UNKNOWN_ERROR;
-        }
-        eglDestroySyncKHR(eglDisplay, eglFence);
-    }
-#endif
-
     sp<IProducerListener> listener;
     { // Autolock scope
         std::lock_guard<std::mutex> lock(mCore->mMutex);
diff --git a/libs/gui/ConsumerBase.cpp b/libs/gui/ConsumerBase.cpp
index 504509d..3ad0e52 100644
--- a/libs/gui/ConsumerBase.cpp
+++ b/libs/gui/ConsumerBase.cpp
@@ -656,9 +656,13 @@
     return OK;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+status_t ConsumerBase::releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer) {
+#else
 status_t ConsumerBase::releaseBufferLocked(
         int slot, const sp<GraphicBuffer> graphicBuffer,
         EGLDisplay display, EGLSyncKHR eglFence) {
+#endif
     if (mAbandoned) {
         CB_LOGE("releaseBufferLocked: ConsumerBase is abandoned!");
         return NO_INIT;
@@ -675,8 +679,12 @@
 
     CB_LOGV("releaseBufferLocked: slot=%d/%" PRIu64,
             slot, mSlots[slot].mFrameNumber);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+    status_t err = mConsumer->releaseBuffer(slot, mSlots[slot].mFrameNumber, mSlots[slot].mFence);
+#else
     status_t err = mConsumer->releaseBuffer(slot, mSlots[slot].mFrameNumber,
             display, eglFence, mSlots[slot].mFence);
+#endif
     if (err == IGraphicBufferConsumer::STALE_BUFFER_SLOT) {
         freeBufferLocked(slot);
     }
diff --git a/libs/gui/GLConsumer.cpp b/libs/gui/GLConsumer.cpp
index 168129b..052b8ed 100644
--- a/libs/gui/GLConsumer.cpp
+++ b/libs/gui/GLConsumer.cpp
@@ -417,18 +417,18 @@
 }
 #endif
 
-status_t GLConsumer::releaseBufferLocked(int buf,
-        sp<GraphicBuffer> graphicBuffer,
-        EGLDisplay display, EGLSyncKHR eglFence) {
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+status_t GLConsumer::releaseBufferLocked(int buf, sp<GraphicBuffer> graphicBuffer,
+                                         EGLDisplay display, EGLSyncKHR eglFence) {
     // release the buffer if it hasn't already been discarded by the
     // BufferQueue. This can happen, for example, when the producer of this
     // buffer has reallocated the original buffer slot after this buffer
     // was acquired.
-    status_t err = ConsumerBase::releaseBufferLocked(
-            buf, graphicBuffer, display, eglFence);
+    status_t err = ConsumerBase::releaseBufferLocked(buf, graphicBuffer, display, eglFence);
     mEglSlots[buf].mEglFence = EGL_NO_SYNC_KHR;
     return err;
 }
+#endif
 
 status_t GLConsumer::updateAndReleaseLocked(const BufferItem& item,
         PendingRelease* pendingRelease)
@@ -490,9 +490,14 @@
     // release old buffer
     if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
         if (pendingRelease == nullptr) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+            status_t status =
+                    releaseBufferLocked(mCurrentTexture, mCurrentTextureImage->graphicBuffer());
+#else
             status_t status = releaseBufferLocked(
                     mCurrentTexture, mCurrentTextureImage->graphicBuffer(),
                     mEglDisplay, mEglSlots[mCurrentTexture].mEglFence);
+#endif
             if (status < NO_ERROR) {
                 GLC_LOGE("updateAndRelease: failed to release buffer: %s (%d)",
                         strerror(-status), status);
@@ -501,10 +506,7 @@
             }
         } else {
             pendingRelease->currentTexture = mCurrentTexture;
-            pendingRelease->graphicBuffer =
-                    mCurrentTextureImage->graphicBuffer();
-            pendingRelease->display = mEglDisplay;
-            pendingRelease->fence = mEglSlots[mCurrentTexture].mEglFence;
+            pendingRelease->graphicBuffer = mCurrentTextureImage->graphicBuffer();
             pendingRelease->isPending = true;
         }
     }
@@ -744,6 +746,11 @@
                 return err;
             }
         } else if (mUseFenceSync && SyncFeatures::getInstance().useFenceSync()) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+            // Basically all clients are using native fence syncs. If they aren't, we lose nothing
+            // by waiting here, because the alternative can cause deadlocks (b/339705065).
+            glFinish();
+#else
             EGLSyncKHR fence = mEglSlots[mCurrentTexture].mEglFence;
             if (fence != EGL_NO_SYNC_KHR) {
                 // There is already a fence for the current slot.  We need to
@@ -773,6 +780,7 @@
             }
             glFlush();
             mEglSlots[mCurrentTexture].mEglFence = fence;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
         }
     }
 
diff --git a/libs/gui/IGraphicBufferConsumer.cpp b/libs/gui/IGraphicBufferConsumer.cpp
index c1b6568..e133532 100644
--- a/libs/gui/IGraphicBufferConsumer.cpp
+++ b/libs/gui/IGraphicBufferConsumer.cpp
@@ -85,6 +85,12 @@
         return callRemote<Signature>(Tag::ATTACH_BUFFER, slot, buffer);
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+    status_t releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) override {
+        using Signature = status_t (IGraphicBufferConsumer::*)(int, uint64_t, const sp<Fence>&);
+        return callRemote<Signature>(Tag::RELEASE_BUFFER, buf, frameNumber, releaseFence);
+    }
+#else
     status_t releaseBuffer(int buf, uint64_t frameNumber,
                            EGLDisplay display __attribute__((unused)),
                            EGLSyncKHR fence __attribute__((unused)),
@@ -92,6 +98,7 @@
         using Signature = status_t (IGraphicBufferConsumer::*)(int, uint64_t, const sp<Fence>&);
         return callRemote<Signature>(Tag::RELEASE_BUFFER, buf, frameNumber, releaseFence);
     }
+#endif
 
     status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) override {
         using Signature = decltype(&IGraphicBufferConsumer::consumerConnect);
diff --git a/libs/gui/include/gui/BufferQueueConsumer.h b/libs/gui/include/gui/BufferQueueConsumer.h
index e00c44e..f99b54b 100644
--- a/libs/gui/include/gui/BufferQueueConsumer.h
+++ b/libs/gui/include/gui/BufferQueueConsumer.h
@@ -65,13 +65,14 @@
     // any references to the just-released buffer that it might have, as if it
     // had received a onBuffersReleased() call with a mask set for the released
     // buffer.
-    //
-    // Note that the dependencies on EGL will be removed once we switch to using
-    // the Android HW Sync HAL.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+    virtual status_t releaseBuffer(int slot, uint64_t frameNumber,
+                                   const sp<Fence>& releaseFence) override;
+#else
     virtual status_t releaseBuffer(int slot, uint64_t frameNumber,
             const sp<Fence>& releaseFence, EGLDisplay display,
             EGLSyncKHR fence);
-
+#endif
     // connect connects a consumer to the BufferQueue.  Only one
     // consumer may be connected, and when that consumer disconnects the
     // BufferQueue is placed into the "abandoned" state, causing most
@@ -167,6 +168,7 @@
     // dump our state in a String
     status_t dumpState(const String8& prefix, String8* outResult) const override;
 
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
     // Functions required for backwards compatibility.
     // These will be modified/renamed in IGraphicBufferConsumer and will be
     // removed from this class at that time. See b/13306289.
@@ -176,6 +178,7 @@
             const sp<Fence>& releaseFence) {
         return releaseBuffer(buf, frameNumber, releaseFence, display, fence);
     }
+#endif
 
     virtual status_t consumerConnect(const sp<IConsumerListener>& consumer,
             bool controlledByApp) {
diff --git a/libs/gui/include/gui/ConsumerBase.h b/libs/gui/include/gui/ConsumerBase.h
index 5cd19c1..acb0006 100644
--- a/libs/gui/include/gui/ConsumerBase.h
+++ b/libs/gui/include/gui/ConsumerBase.h
@@ -245,10 +245,13 @@
     // must take place when a buffer is released back to the BufferQueue.  If
     // it is overridden the derived class's implementation must call
     // ConsumerBase::releaseBufferLocked.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+    virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer);
+#else
     virtual status_t releaseBufferLocked(int slot,
             const sp<GraphicBuffer> graphicBuffer,
             EGLDisplay display = EGL_NO_DISPLAY, EGLSyncKHR eglFence = EGL_NO_SYNC_KHR);
-
+#endif
     // returns true iff the slot still has the graphicBuffer in it.
     bool stillTracking(int slot, const sp<GraphicBuffer> graphicBuffer);
 
diff --git a/libs/gui/include/gui/GLConsumer.h b/libs/gui/include/gui/GLConsumer.h
index 30cbfa2..dbf707f 100644
--- a/libs/gui/include/gui/GLConsumer.h
+++ b/libs/gui/include/gui/GLConsumer.h
@@ -271,6 +271,7 @@
 #endif
     // releaseBufferLocked overrides the ConsumerBase method to update the
     // mEglSlots array in addition to the ConsumerBase.
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
     virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer,
                                          EGLDisplay display = EGL_NO_DISPLAY,
                                          EGLSyncKHR eglFence = EGL_NO_SYNC_KHR) override;
@@ -279,16 +280,14 @@
             const sp<GraphicBuffer> graphicBuffer, EGLSyncKHR eglFence) {
         return releaseBufferLocked(slot, graphicBuffer, mEglDisplay, eglFence);
     }
+#endif
 
     struct PendingRelease {
-        PendingRelease() : isPending(false), currentTexture(-1),
-                graphicBuffer(), display(nullptr), fence(nullptr) {}
+        PendingRelease() : isPending(false), currentTexture(-1), graphicBuffer() {}
 
         bool isPending;
         int currentTexture;
         sp<GraphicBuffer> graphicBuffer;
-        EGLDisplay display;
-        EGLSyncKHR fence;
     };
 
     // This releases the buffer in the slot referenced by mCurrentTexture,
@@ -468,16 +467,18 @@
     // EGLSlot contains the information and object references that
     // GLConsumer maintains about a BufferQueue buffer slot.
     struct EglSlot {
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
         EglSlot() : mEglFence(EGL_NO_SYNC_KHR) {}
-
+#endif
         // mEglImage is the EGLImage created from mGraphicBuffer.
         sp<EglImage> mEglImage;
-
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
         // mFence is the EGL sync object that must signal before the buffer
         // associated with this buffer slot may be dequeued. It is initialized
         // to EGL_NO_SYNC_KHR when the buffer is created and (optionally, based
         // on a compile-time option) set to a new sync object in updateTexImage.
         EGLSyncKHR mEglFence;
+#endif
     };
 
     // mEglDisplay is the EGLDisplay with which this GLConsumer is currently
diff --git a/libs/gui/include/gui/IGraphicBufferConsumer.h b/libs/gui/include/gui/IGraphicBufferConsumer.h
index 56eb291..f6b3e89 100644
--- a/libs/gui/include/gui/IGraphicBufferConsumer.h
+++ b/libs/gui/include/gui/IGraphicBufferConsumer.h
@@ -139,12 +139,17 @@
     //               * the buffer slot was invalid
     //               * the fence was NULL
     //               * the buffer slot specified is not in the acquired state
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+    virtual status_t releaseBuffer(int buf, uint64_t frameNumber,
+                                   const sp<Fence>& releaseFence) = 0;
+#else
     virtual status_t releaseBuffer(int buf, uint64_t frameNumber, EGLDisplay display,
                                    EGLSyncKHR fence, const sp<Fence>& releaseFence) = 0;
 
     status_t releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) {
         return releaseBuffer(buf, frameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, releaseFence);
     }
+#endif
 
     // consumerConnect connects a consumer to the BufferQueue. Only one consumer may be connected,
     // and when that consumer disconnects the BufferQueue is placed into the "abandoned" state,
diff --git a/libs/gui/include/gui/mock/GraphicBufferConsumer.h b/libs/gui/include/gui/mock/GraphicBufferConsumer.h
index 98f24c2..8dfd3cb 100644
--- a/libs/gui/include/gui/mock/GraphicBufferConsumer.h
+++ b/libs/gui/include/gui/mock/GraphicBufferConsumer.h
@@ -18,6 +18,7 @@
 
 #include <gmock/gmock.h>
 
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/IGraphicBufferConsumer.h>
 
 #include <utils/RefBase.h>
@@ -33,7 +34,11 @@
     MOCK_METHOD3(acquireBuffer, status_t(BufferItem*, nsecs_t, uint64_t));
     MOCK_METHOD1(detachBuffer, status_t(int));
     MOCK_METHOD2(attachBuffer, status_t(int*, const sp<GraphicBuffer>&));
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+    MOCK_METHOD3(releaseBuffer, status_t(int, uint64_t, const sp<Fence>&));
+#else
     MOCK_METHOD5(releaseBuffer, status_t(int, uint64_t, EGLDisplay, EGLSyncKHR, const sp<Fence>&));
+#endif
     MOCK_METHOD2(consumerConnect, status_t(const sp<IConsumerListener>&, bool));
     MOCK_METHOD0(consumerDisconnect, status_t());
     MOCK_METHOD1(getReleasedBuffers, status_t(uint64_t*));
diff --git a/libs/input/AccelerationCurve.cpp b/libs/input/AccelerationCurve.cpp
index 0b47f3e..1ed9794 100644
--- a/libs/input/AccelerationCurve.cpp
+++ b/libs/input/AccelerationCurve.cpp
@@ -47,9 +47,9 @@
 // in faster pointer movements.
 //
 // The base gain is calculated using a linear mapping function that maps the
-// sensitivity range [-7, 7] to a base gain range [0.5, 2.0].
+// sensitivity range [-7, 7] to a base gain range [1.0, 3.5].
 double calculateBaseGain(int32_t sensitivity) {
-    return 0.5 + (sensitivity + 7) * (2.0 - 0.5) / (7 + 7);
+    return 1.0 + (sensitivity + 7) * (3.5 - 1.0) / (7 + 7);
 }
 
 } // namespace
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 6ce3fba..4b87dab 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -43,8 +43,9 @@
     const int INVALID_INPUT_DEVICE_ID = -2;
 
     /**
-     * The input event was injected from accessibility. Used in policyFlags for input event
-     * injection.
+     * The input event was injected from some AccessibilityService, which may be either an
+     * Accessibility Tool OR a service using that API for purposes other than assisting users with
+     * disabilities. Used in policyFlags for input event injection.
      */
     const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000;
 
@@ -55,18 +56,33 @@
     const int POLICY_FLAG_KEY_GESTURE_TRIGGERED = 0x40000;
 
     /**
+     * The input event was injected from an AccessibilityService with the
+     * AccessibilityServiceInfo#isAccessibilityTool property set to true. These services (known as
+     * "Accessibility Tools") are used to assist users with disabilities, so events from these
+     * services should be able to reach all Views including Views which set
+     * View#isAccessibilityDataSensitive to true. Used in policyFlags for input event injection.
+     */
+    const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL = 0x80000;
+
+    /**
      * Common input event flag used for both motion and key events for a gesture or pointer being
      * canceled.
      */
     const int INPUT_EVENT_FLAG_CANCELED = 0x20;
 
     /**
-     * Common input event flag used for both motion and key events, indicating that the event
-     * was generated or modified by accessibility service.
+     * Input event flag used for both motion and key events.
+     * See POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY for more information.
      */
     const int INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800;
 
     /**
+     * Input event flag used for motion events.
+     * See POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL for more information.
+     */
+    const int INPUT_EVENT_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL = 0x1000;
+
+    /**
      * Common input event flag used for both motion and key events, indicating that the system has
      * detected this event may be inconsistent with the current event sequence or gesture, such as
      * when a pointer move event is sent but the pointer is not down.
diff --git a/libs/input/android/os/MotionEventFlag.aidl b/libs/input/android/os/MotionEventFlag.aidl
index 2093b06..6c9ccfb 100644
--- a/libs/input/android/os/MotionEventFlag.aidl
+++ b/libs/input/android/os/MotionEventFlag.aidl
@@ -118,13 +118,24 @@
     PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = 0x100,
 
     /**
-     * The input event was generated or modified by accessibility service.
-     * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either
-     * set of flags, including in input/Input.h and in android/input.h.
+     * The input event was injected from some AccessibilityService, which may be either an
+     * Accessibility Tool OR a service using that API for purposes other than assisting users with
+     * disabilities. Shared by both KeyEvent and MotionEvent flags, so this value should not
+     * overlap with either set of flags, including in input/Input.h and in android/input.h.
      */
     IS_ACCESSIBILITY_EVENT = IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
 
     /**
+     * The input event was injected from an AccessibilityService with the
+     * AccessibilityServiceInfo#isAccessibilityTool property set to true. These services (known as
+     * "Accessibility Tools") are used to assist users with disabilities, so events from these
+     * services should be able to reach all Views including Views which set
+     * View#isAccessibilityDataSensitive to true. Only used by MotionEvent flags.
+     */
+    INJECTED_FROM_ACCESSIBILITY_TOOL =
+        IInputConstants.INPUT_EVENT_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL,
+
+    /**
      * Private flag that indicates when the system has detected that this motion event
      * may be inconsistent with respect to the sequence of previously delivered motion events,
      * such as when a pointer move event is sent but the pointer is not down.
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index f17575d..72a6fdf 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -79,13 +79,6 @@
 }
 
 flag {
-  name: "enable_input_filter_rust_impl"
-  namespace: "input"
-  description: "Enable input filter rust implementation"
-  bug: "294546335"
-}
-
-flag {
   name: "override_key_behavior_permission_apis"
   is_exported: true
   namespace: "input"
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 90f509d..6956a84 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -219,6 +219,9 @@
                 MotionEventFlag::PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION.0 as u32;
         /// FLAG_IS_ACCESSIBILITY_EVENT
         const IS_ACCESSIBILITY_EVENT = MotionEventFlag::IS_ACCESSIBILITY_EVENT.0 as u32;
+        /// FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL
+        const INJECTED_FROM_ACCESSIBILITY_TOOL =
+                MotionEventFlag::INJECTED_FROM_ACCESSIBILITY_TOOL.0 as u32;
         /// FLAG_TAINTED
         const TAINTED = MotionEventFlag::TAINTED.0 as u32;
         /// FLAG_TARGET_ACCESSIBILITY_FOCUS
diff --git a/libs/nativedisplay/include/surfacetexture/EGLConsumer.h b/libs/nativedisplay/include/surfacetexture/EGLConsumer.h
index 444722b..226a8a6 100644
--- a/libs/nativedisplay/include/surfacetexture/EGLConsumer.h
+++ b/libs/nativedisplay/include/surfacetexture/EGLConsumer.h
@@ -113,18 +113,11 @@
 
 protected:
     struct PendingRelease {
-        PendingRelease()
-              : isPending(false),
-                currentTexture(-1),
-                graphicBuffer(),
-                display(nullptr),
-                fence(nullptr) {}
+        PendingRelease() : isPending(false), currentTexture(-1), graphicBuffer() {}
 
         bool isPending;
         int currentTexture;
         sp<GraphicBuffer> graphicBuffer;
-        EGLDisplay display;
-        EGLSyncKHR fence;
     };
 
     /**
@@ -250,13 +243,16 @@
      * EGLConsumer maintains about a BufferQueue buffer slot.
      */
     struct EglSlot {
-        EglSlot() : mEglFence(EGL_NO_SYNC_KHR) {}
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
 
+        EglSlot() : mEglFence(EGL_NO_SYNC_KHR) {}
+#endif
         /**
          * mEglImage is the EGLImage created from mGraphicBuffer.
          */
         sp<EglImage> mEglImage;
 
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
         /**
          * mFence is the EGL sync object that must signal before the buffer
          * associated with this buffer slot may be dequeued. It is initialized
@@ -264,6 +260,7 @@
          * on a compile-time option) set to a new sync object in updateTexImage.
          */
         EGLSyncKHR mEglFence;
+#endif
     };
 
     /**
diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
index 006a785..253aa18 100644
--- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
+++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
@@ -343,9 +343,13 @@
      * releaseBufferLocked overrides the ConsumerBase method to update the
      * mEglSlots array in addition to the ConsumerBase.
      */
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+    virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer) override;
+#else
     virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer,
                                          EGLDisplay display = EGL_NO_DISPLAY,
                                          EGLSyncKHR eglFence = EGL_NO_SYNC_KHR) override;
+#endif
 
     /**
      * freeBufferLocked frees up the given buffer slot. If the slot has been
diff --git a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
index 3959fce..fad0f6c 100644
--- a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
@@ -221,7 +221,11 @@
 }
 
 void EGLConsumer::onReleaseBufferLocked(int buf) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+    (void)buf;
+#else
     mEglSlots[buf].mEglFence = EGL_NO_SYNC_KHR;
+#endif
 }
 
 status_t EGLConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRelease* pendingRelease,
@@ -283,10 +287,15 @@
     // release old buffer
     if (st.mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
         if (pendingRelease == nullptr) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+            status_t status = st.releaseBufferLocked(st.mCurrentTexture,
+                                                     mCurrentTextureImage->graphicBuffer());
+#else
             status_t status =
                     st.releaseBufferLocked(st.mCurrentTexture,
                                            mCurrentTextureImage->graphicBuffer(), mEglDisplay,
                                            mEglSlots[st.mCurrentTexture].mEglFence);
+#endif
             if (status < NO_ERROR) {
                 EGC_LOGE("updateAndRelease: failed to release buffer: %s (%d)", strerror(-status),
                          status);
@@ -296,8 +305,6 @@
         } else {
             pendingRelease->currentTexture = st.mCurrentTexture;
             pendingRelease->graphicBuffer = mCurrentTextureImage->graphicBuffer();
-            pendingRelease->display = mEglDisplay;
-            pendingRelease->fence = mEglSlots[st.mCurrentTexture].mEglFence;
             pendingRelease->isPending = true;
         }
     }
@@ -502,6 +509,11 @@
                 return err;
             }
         } else if (st.mUseFenceSync && SyncFeatures::getInstance().useFenceSync()) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+            // Basically all clients are using native fence syncs. If they aren't, we lose nothing
+            // by waiting here, because the alternative can cause deadlocks (b/339705065).
+            glFinish();
+#else
             EGLSyncKHR fence = mEglSlots[st.mCurrentTexture].mEglFence;
             if (fence != EGL_NO_SYNC_KHR) {
                 // There is already a fence for the current slot.  We need to
@@ -531,6 +543,7 @@
             }
             glFlush();
             mEglSlots[st.mCurrentTexture].mEglFence = fence;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
         }
     }
 
diff --git a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
index 60e87b5..1ffd382 100644
--- a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+#define EGL_EGLEXT_PROTOTYPES
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
 #include <gui/BufferQueue.h>
 #include <surfacetexture/ImageConsumer.h>
 #include <surfacetexture/SurfaceTexture.h>
@@ -95,10 +99,34 @@
         }
 
         // Finally release the old buffer.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+        EGLSyncKHR previousFence = mImageSlots[st.mCurrentTexture].eglFence();
+        if (previousFence != EGL_NO_SYNC_KHR) {
+            // Most platforms will be using native fences, so it's unlikely that we'll ever have to
+            // process an eglFence. Ideally we can remove this code eventually. In the mean time, do
+            // our best to wait for it so the buffer stays valid, otherwise return an error to the
+            // caller.
+            //
+            // EGL_SYNC_FLUSH_COMMANDS_BIT_KHR so that we don't wait forever on a fence that hasn't
+            // shown up on the GPU yet.
+            EGLint result = eglClientWaitSyncKHR(display, previousFence,
+                                                 EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, 1000000000);
+            if (result == EGL_FALSE) {
+                IMG_LOGE("dequeueBuffer: error %#x waiting for fence", eglGetError());
+            } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
+                IMG_LOGE("dequeueBuffer: timeout waiting for fence");
+            }
+            eglDestroySyncKHR(display, previousFence);
+        }
+
+        status_t status = st.releaseBufferLocked(st.mCurrentTexture,
+                                                 st.mSlots[st.mCurrentTexture].mGraphicBuffer);
+#else
         status_t status =
                 st.releaseBufferLocked(st.mCurrentTexture,
                                        st.mSlots[st.mCurrentTexture].mGraphicBuffer, display,
                                        mImageSlots[st.mCurrentTexture].eglFence());
+#endif
         if (status < NO_ERROR) {
             IMG_LOGE("dequeueImage: failed to release buffer: %s (%d)", strerror(-status), status);
             err = status;
diff --git a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
index ce232cc..c0a1cc5 100644
--- a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
+++ b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
@@ -178,13 +178,21 @@
     return NO_ERROR;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+status_t SurfaceTexture::releaseBufferLocked(int buf, sp<GraphicBuffer> graphicBuffer) {
+#else
 status_t SurfaceTexture::releaseBufferLocked(int buf, sp<GraphicBuffer> graphicBuffer,
                                              EGLDisplay display, EGLSyncKHR eglFence) {
+#endif
     // release the buffer if it hasn't already been discarded by the
     // BufferQueue. This can happen, for example, when the producer of this
     // buffer has reallocated the original buffer slot after this buffer
     // was acquired.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+    status_t err = ConsumerBase::releaseBufferLocked(buf, graphicBuffer);
+#else
     status_t err = ConsumerBase::releaseBufferLocked(buf, graphicBuffer, display, eglFence);
+#endif
     // We could be releasing an EGL/Vulkan buffer, even if not currently
     // attached to a GL context.
     mImageConsumer.onReleaseBufferLocked(buf);
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 14d08ee..5f2d1b1 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -838,8 +838,7 @@
             LOG_ALWAYS_FATAL_IF(activeSurface == dstSurface);
             LOG_ALWAYS_FATAL_IF(canvas == dstCanvas);
 
-            // save a snapshot of the activeSurface to use as input to the blur shaders
-            blurInput = activeSurface->makeImageSnapshot();
+            blurInput = activeSurface->makeTemporaryImage();
 
             // blit the offscreen framebuffer into the destination AHB. This ensures that
             // even if the blurred image does not cover the screen (for example, during
@@ -853,12 +852,9 @@
                     dstCanvas->drawAnnotation(SkRect::Make(dstCanvas->imageInfo().dimensions()),
                                               String8::format("SurfaceID|%" PRId64, id).c_str(),
                                               nullptr);
-                    dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint);
-                } else {
-                    activeSurface->draw(dstCanvas, 0, 0, SkSamplingOptions(), &paint);
                 }
+                dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint);
             }
-
             // assign dstCanvas to canvas and ensure that the canvas state is up to date
             canvas = dstCanvas;
             surfaceAutoSaveRestore.replace(canvas);
@@ -891,12 +887,6 @@
         if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha)) {
             std::unordered_map<uint32_t, sk_sp<SkImage>> cachedBlurs;
 
-            // if multiple layers have blur, then we need to take a snapshot now because
-            // only the lowest layer will have blurImage populated earlier
-            if (!blurInput) {
-                blurInput = activeSurface->makeImageSnapshot();
-            }
-
             // rect to be blurred in the coordinate space of blurInput
             SkRect blurRect = canvas->getTotalMatrix().mapRect(bounds.rect());
 
@@ -920,6 +910,29 @@
 
             // TODO(b/182216890): Filter out empty layers earlier
             if (blurRect.width() > 0 && blurRect.height() > 0) {
+                // if multiple layers have blur, then we need to take a snapshot now because
+                // only the lowest layer will have blurImage populated earlier
+                if (!blurInput) {
+                    bool requiresCrossFadeWithBlurInput = false;
+                    if (layer.backgroundBlurRadius > 0 &&
+                        layer.backgroundBlurRadius < mBlurFilter->getMaxCrossFadeRadius()) {
+                        requiresCrossFadeWithBlurInput = true;
+                    }
+                    for (auto region : layer.blurRegions) {
+                        if (region.blurRadius < mBlurFilter->getMaxCrossFadeRadius()) {
+                            requiresCrossFadeWithBlurInput = true;
+                        }
+                    }
+                    if (requiresCrossFadeWithBlurInput) {
+                        // If we require cross fading with the blur input, we need to make sure we
+                        // make a copy of the surface to the image since we will be writing to the
+                        // surface while sampling the blurInput.
+                        blurInput = activeSurface->makeImageSnapshot();
+                    } else {
+                        blurInput = activeSurface->makeTemporaryImage();
+                    }
+                }
+
                 if (layer.backgroundBlurRadius > 0) {
                     SFTRACE_NAME("BackgroundBlur");
                     auto blurredImage = mBlurFilter->generate(context, layer.backgroundBlurRadius,
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index cd1bd71..8edf98e 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -90,6 +90,8 @@
                                                      linearSampling, &blurMatrix);
 
     if (blurRadius < mMaxCrossFadeRadius) {
+        LOG_ALWAYS_FATAL_IF(!input);
+
         // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
         // case you might expect the matrix to simply be the canvas matrix.
         SkMatrix inputMatrix;
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
index 8c52c57..b03ebe3 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
@@ -64,7 +64,7 @@
             SkSamplingOptions{SkFilterMode::kLinear, SkMipmapMode::kNone},
             &paint,
             SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint);
-    return surface->makeImageSnapshot();
+    return surface->makeTemporaryImage();
 }
 
 } // namespace skia
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
index ef57c30..897f5cf 100644
--- a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
@@ -167,14 +167,14 @@
     }
     // Next the remaining downscale blur passes.
     for (int i = 0; i < filterPasses; i++) {
-        blurInto(surfaces[i + 1], surfaces[i]->makeImageSnapshot(), kWeights[1 + i] * step, 1.0f);
+        blurInto(surfaces[i + 1], surfaces[i]->makeTemporaryImage(), kWeights[1 + i] * step, 1.0f);
     }
     // Finally blur+upscale back to our original size.
     for (int i = filterPasses - 1; i >= 0; i--) {
-        blurInto(surfaces[i], surfaces[i + 1]->makeImageSnapshot(), kWeights[4 - i] * step,
+        blurInto(surfaces[i], surfaces[i + 1]->makeTemporaryImage(), kWeights[4 - i] * step,
                  std::min(1.0f, filterDepth - i));
     }
-    return surfaces[0]->makeImageSnapshot();
+    return surfaces[0]->makeTemporaryImage();
 }
 
 } // namespace skia
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
index defaf6e..f71a63d 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
@@ -70,7 +70,7 @@
     paint.setShader(std::move(shader));
     paint.setBlendMode(SkBlendMode::kSrc);
     surface->getCanvas()->drawPaint(paint);
-    return surface->makeImageSnapshot();
+    return surface->makeTemporaryImage();
 }
 
 sk_sp<SkImage> KawaseBlurFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius,
diff --git a/libs/renderengine/skia/filters/MouriMap.cpp b/libs/renderengine/skia/filters/MouriMap.cpp
index aa12cef..5dc36e6 100644
--- a/libs/renderengine/skia/filters/MouriMap.cpp
+++ b/libs/renderengine/skia/filters/MouriMap.cpp
@@ -104,7 +104,7 @@
     paint.setShader(std::move(shader));
     paint.setBlendMode(SkBlendMode::kSrc);
     surface->getCanvas()->drawPaint(paint);
-    return surface->makeImageSnapshot();
+    return surface->makeTemporaryImage();
 }
 
 } // namespace
diff --git a/libs/tracing_perfetto/include/tracing_sdk.h b/libs/tracing_perfetto/include/tracing_sdk.h
index 4a6e849..800bf3c 100644
--- a/libs/tracing_perfetto/include/tracing_sdk.h
+++ b/libs/tracing_perfetto/include/tracing_sdk.h
@@ -27,6 +27,10 @@
 #include "perfetto/public/te_macros.h"
 #include "perfetto/public/track_event.h"
 
+#include "perfetto/public/abi/pb_decoder_abi.h"
+#include "perfetto/public/pb_utils.h"
+#include "perfetto/public/tracing_session.h"
+
 /**
  * The objects declared here are intended to be managed by Java.
  * This means the Java Garbage Collector is responsible for freeing the
@@ -452,6 +456,22 @@
   std::vector<PerfettoTeHlProtoField*> fields_;
 };
 
+class Session {
+ public:
+  Session(bool is_backend_in_process, void* buf, size_t len);
+  ~Session();
+  Session(const Session&) = delete;
+  Session& operator=(const Session&) = delete;
+
+  bool FlushBlocking(uint32_t timeout_ms);
+  void StopBlocking();
+  std::vector<uint8_t> ReadBlocking();
+
+  static void delete_session(Session* session);
+
+  struct PerfettoTracingSessionImpl* session_ = nullptr;
+};
+
 /**
  * @brief Activates a trigger.
  * @param name The name of the trigger.
diff --git a/libs/tracing_perfetto/tests/Android.bp b/libs/tracing_perfetto/tests/Android.bp
index 0dab517..79fb704 100644
--- a/libs/tracing_perfetto/tests/Android.bp
+++ b/libs/tracing_perfetto/tests/Android.bp
@@ -21,44 +21,12 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_static {
-    name: "libtracing_perfetto_test_utils",
-    export_include_dirs: [
-        "include",
-    ],
-    static_libs: [
-        "libflagtest",
-        "libgmock",
-        "perfetto_trace_protos",
-    ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-enum-compare",
-        "-Wno-unused-function",
-    ],
-
-    srcs: [
-        "utils.cpp",
-    ],
-
-    shared_libs: [
-        "libperfetto_c",
-        "liblog",
-        "libprotobuf-cpp-lite",
-    ],
-    export_shared_lib_headers: [
-        "libperfetto_c",
-    ],
-}
-
 cc_test {
     name: "libtracing_perfetto_tests",
     static_libs: [
         "libflagtest",
         "libgmock",
         "perfetto_trace_protos",
-        "libtracing_perfetto_test_utils",
     ],
     cflags: [
         "-Wall",
@@ -68,12 +36,15 @@
         "android.os.flags-aconfig-cc-host",
         "libbase",
         "libperfetto_c",
-        "liblog",
         "libprotobuf-cpp-lite",
         "libtracing_perfetto",
     ],
     srcs: [
         "tracing_perfetto_test.cpp",
+        "utils.cpp",
+    ],
+    local_include_dirs: [
+        "include",
     ],
     test_suites: ["device-tests"],
 }
diff --git a/libs/tracing_perfetto/tracing_sdk.cpp b/libs/tracing_perfetto/tracing_sdk.cpp
index 02e8d10..c97e900 100644
--- a/libs/tracing_perfetto/tracing_sdk.cpp
+++ b/libs/tracing_perfetto/tracing_sdk.cpp
@@ -254,6 +254,47 @@
   return &field_;
 }
 
+Session::Session(bool is_backend_in_process, void* buf, size_t len) {
+  session_ = PerfettoTracingSessionCreate(is_backend_in_process
+                                              ? PERFETTO_BACKEND_IN_PROCESS
+                                              : PERFETTO_BACKEND_SYSTEM);
+
+  PerfettoTracingSessionSetup(session_, buf, len);
+
+  PerfettoTracingSessionStartBlocking(session_);
+}
+
+Session::~Session() {
+  PerfettoTracingSessionStopBlocking(session_);
+  PerfettoTracingSessionDestroy(session_);
+}
+
+bool Session::FlushBlocking(uint32_t timeout_ms) {
+  return PerfettoTracingSessionFlushBlocking(session_, timeout_ms);
+}
+
+void Session::StopBlocking() {
+  PerfettoTracingSessionStopBlocking(session_);
+}
+
+std::vector<uint8_t> Session::ReadBlocking() {
+  std::vector<uint8_t> data;
+  PerfettoTracingSessionReadTraceBlocking(
+      session_,
+      [](struct PerfettoTracingSessionImpl*, const void* trace_data,
+         size_t size, bool, void* user_arg) {
+        auto& dst = *static_cast<std::vector<uint8_t>*>(user_arg);
+        auto* src = static_cast<const uint8_t*>(trace_data);
+        dst.insert(dst.end(), src, src + size);
+      },
+      &data);
+  return data;
+}
+
+void Session::delete_session(Session* ptr) {
+  delete ptr;
+}
+
 void activate_trigger(const char* name, uint32_t ttl_ms) {
   const char* names[] = {name, nullptr};
   PerfettoProducerActivateTriggers(names, ttl_ms);
diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
index ee38f50..b7ef9b3 100644
--- a/libs/ui/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -26,6 +26,7 @@
 #include <string>
 #include <string_view>
 
+#include <ftl/concat.h>
 #include <ftl/hash.h>
 #include <log/log.h>
 #include <ui/DisplayIdentification.h>
@@ -423,4 +424,27 @@
     return PhysicalDisplayId::fromEdid(0, kVirtualEdidManufacturerId, id);
 }
 
+PhysicalDisplayId generateEdidDisplayId(const Edid& edid) {
+    const ftl::Concat displayDetailsString{edid.manufacturerId,
+                                           edid.productId,
+                                           ftl::truncated<13>(edid.displayName),
+                                           edid.manufactureWeek,
+                                           edid.manufactureOrModelYear,
+                                           edid.physicalSizeInCm.getWidth(),
+                                           edid.physicalSizeInCm.getHeight()};
+
+    // String has to be cropped to 64 characters (at most) for ftl::stable_hash.
+    // This is fine as the accuracy or completeness of the above fields is not
+    // critical for a ID fabrication.
+    const std::optional<uint64_t> hashedDisplayDetailsOpt =
+            ftl::stable_hash(std::string_view(displayDetailsString.c_str(), 64));
+
+    // Combine the hashes via bit-shifted XORs.
+    const uint64_t id = (hashedDisplayDetailsOpt.value_or(0) << 17) ^
+            (edid.hashedBlockZeroSerialNumberOpt.value_or(0) >> 11) ^
+            (edid.hashedDescriptorBlockSerialNumberOpt.value_or(0) << 23);
+
+    return PhysicalDisplayId::fromEdidHash(id);
+}
+
 } // namespace android
diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h
index 9ea6cec..13ed754 100644
--- a/libs/ui/include/ui/DisplayId.h
+++ b/libs/ui/include/ui/DisplayId.h
@@ -81,12 +81,17 @@
         return PhysicalDisplayId(id);
     }
 
-    // Returns a stable ID based on EDID information.
+    // Returns a stable ID based on EDID and port information.
     static constexpr PhysicalDisplayId fromEdid(uint8_t port, uint16_t manufacturerId,
                                                 uint32_t modelHash) {
         return PhysicalDisplayId(FLAG_STABLE, port, manufacturerId, modelHash);
     }
 
+    // Returns a stable and consistent ID based exclusively on EDID information.
+    static constexpr PhysicalDisplayId fromEdidHash(uint64_t hashedEdid) {
+        return PhysicalDisplayId(hashedEdid);
+    }
+
     // Returns an unstable ID. If EDID is available using "fromEdid" is preferred.
     static constexpr PhysicalDisplayId fromPort(uint8_t port) {
         constexpr uint16_t kManufacturerId = 0;
@@ -103,6 +108,8 @@
     // Flag indicating that the ID is stable across reboots.
     static constexpr uint64_t FLAG_STABLE = 1ULL << 62;
 
+    using DisplayId::DisplayId;
+
     constexpr PhysicalDisplayId(uint64_t flags, uint8_t port, uint16_t manufacturerId,
                                 uint32_t modelHash)
           : DisplayId(flags | (static_cast<uint64_t>(manufacturerId) << 40) |
diff --git a/libs/ui/include/ui/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h
index 5883dba..1e3449c 100644
--- a/libs/ui/include/ui/DisplayIdentification.h
+++ b/libs/ui/include/ui/DisplayIdentification.h
@@ -74,6 +74,7 @@
     std::optional<uint64_t> hashedDescriptorBlockSerialNumberOpt;
     PnpId pnpId;
     uint32_t modelHash;
+    // Up to 13 characters of ASCII text terminated by LF and padded with SP.
     std::string_view displayName;
     uint8_t manufactureOrModelYear;
     uint8_t manufactureWeek;
@@ -91,4 +92,8 @@
 
 PhysicalDisplayId getVirtualDisplayId(uint32_t id);
 
+// Generates a consistent, stable, and hashed display ID that is based on the
+// display's parsed EDID fields.
+PhysicalDisplayId generateEdidDisplayId(const Edid& edid);
+
 } // namespace android
diff --git a/libs/ui/tests/DisplayIdentification_test.cpp b/libs/ui/tests/DisplayIdentification_test.cpp
index fdcf112..75c71a5 100644
--- a/libs/ui/tests/DisplayIdentification_test.cpp
+++ b/libs/ui/tests/DisplayIdentification_test.cpp
@@ -376,6 +376,22 @@
     EXPECT_EQ(4633127902230889474, tertiaryInfo->id.value);
 }
 
+TEST(DisplayIdentificationTest, generateEdidDisplayId) {
+    const auto firstExternalDisplayEdidOpt = parseEdid(getExternalEdid());
+    ASSERT_TRUE(firstExternalDisplayEdidOpt);
+    const PhysicalDisplayId firstExternalDisplayId =
+            generateEdidDisplayId(firstExternalDisplayEdidOpt.value());
+
+    const auto secondExternalDisplayEdidOpt = parseEdid(getExternalEedid());
+    ASSERT_TRUE(secondExternalDisplayEdidOpt);
+    const PhysicalDisplayId secondExternalDisplayId =
+            generateEdidDisplayId(secondExternalDisplayEdidOpt.value());
+
+    // Display IDs should be unique.
+    EXPECT_EQ(4067182673952280501u, firstExternalDisplayId.value);
+    EXPECT_EQ(14712168404707886855u, secondExternalDisplayId.value);
+}
+
 TEST(DisplayIdentificationTest, deviceProductInfo) {
     using ManufactureYear = DeviceProductInfo::ManufactureYear;
     using ManufactureWeekAndYear = DeviceProductInfo::ManufactureWeekAndYear;
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index b155122..7d62ed9 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -41,8 +41,6 @@
 const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
         sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
 
-const bool ENABLE_INPUT_FILTER_RUST = input_flags::enable_input_filter_rust_impl();
-
 int32_t exceptionCodeFromStatusT(status_t status) {
     switch (status) {
         case OK:
@@ -134,12 +132,10 @@
     mTracingStages.emplace_back(
             std::make_unique<TracedInputListener>("InputDispatcher", *mDispatcher));
 
-    if (ENABLE_INPUT_FILTER_RUST) {
-        mInputFilter = std::make_unique<InputFilter>(*mTracingStages.back(), *mInputFlingerRust,
-                                                     inputFilterPolicy);
-        mTracingStages.emplace_back(
-                std::make_unique<TracedInputListener>("InputFilter", *mInputFilter));
-    }
+    mInputFilter = std::make_unique<InputFilter>(*mTracingStages.back(), *mInputFlingerRust,
+                                                 inputFilterPolicy);
+    mTracingStages.emplace_back(
+            std::make_unique<TracedInputListener>("InputFilter", *mInputFilter));
 
     if (ENABLE_INPUT_DEVICE_USAGE_METRICS) {
         mCollector = std::make_unique<InputDeviceMetricsCollector>(*mTracingStages.back());
@@ -250,10 +246,8 @@
         mCollector->dump(dump);
         dump += '\n';
     }
-    if (ENABLE_INPUT_FILTER_RUST) {
-        mInputFilter->dump(dump);
-        dump += '\n';
-    }
+    mInputFilter->dump(dump);
+    dump += '\n';
     mDispatcher->dump(dump);
     dump += '\n';
 }
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 5bf6ebb..9f91285 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -1017,9 +1017,9 @@
                         sourceBoundary == DisplayTopologyPosition::BOTTOM
                 ? (destinationViewport->logicalRight - destinationViewport->logicalLeft)
                 : (destinationViewport->logicalBottom - destinationViewport->logicalTop);
-        if (cursorOffset >= adjacentDisplay.offsetPx &&
-            cursorOffset <= adjacentDisplay.offsetPx + edgeSize) {
-            return std::make_pair(destinationViewport, adjacentDisplay.offsetPx);
+        if (cursorOffset >= adjacentDisplay.offsetDp &&
+            cursorOffset <= adjacentDisplay.offsetDp + edgeSize) {
+            return std::make_pair(destinationViewport, adjacentDisplay.offsetDp);
         }
     }
     return std::nullopt;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index beb4c92..493e480 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -918,6 +918,21 @@
     return inputTarget;
 }
 
+std::string dumpWindowForTouchOcclusion(const WindowInfo& info, bool isTouchedWindow) {
+    return StringPrintf(INDENT2 "* %spackage=%s/%s, id=%" PRId32 ", mode=%s, alpha=%.2f, "
+                                "frame=[%" PRId32 ",%" PRId32 "][%" PRId32 ",%" PRId32
+                                "], touchableRegion=%s, window={%s}, inputConfig={%s}, "
+                                "hasToken=%s, applicationInfo.name=%s, applicationInfo.token=%s\n",
+                        isTouchedWindow ? "[TOUCHED] " : "", info.packageName.c_str(),
+                        info.ownerUid.toString().c_str(), info.id,
+                        toString(info.touchOcclusionMode).c_str(), info.alpha, info.frame.left,
+                        info.frame.top, info.frame.right, info.frame.bottom,
+                        dumpRegion(info.touchableRegion).c_str(), info.name.c_str(),
+                        info.inputConfig.string().c_str(), toString(info.token != nullptr),
+                        info.applicationInfo.name.c_str(),
+                        binderToString(info.applicationInfo.token).c_str());
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -933,13 +948,12 @@
         mLastDropReason(DropReason::NOT_DROPPED),
         mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER),
         mMinTimeBetweenUserActivityPokes(DEFAULT_USER_ACTIVITY_POKE_INTERVAL),
+        mConnectionManager(mLooper),
         mNextUnblockedEvent(nullptr),
         mMonitorDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT),
         mDispatchEnabled(false),
         mDispatchFrozen(false),
         mInputFilterEnabled(false),
-        mMaximumObscuringOpacityForTouch(1.0f),
-        mConnectionManager(mLooper),
         mFocusedDisplayId(ui::LogicalDisplayId::DEFAULT),
         mWindowTokenWithPointerCapture(nullptr),
         mAwaitedApplicationDisplayId(ui::LogicalDisplayId::INVALID),
@@ -2485,9 +2499,9 @@
             if (isSplit) {
                 targetFlags |= InputTarget::Flags::SPLIT;
             }
-            if (isWindowObscuredAtPointLocked(windowHandle, x, y)) {
+            if (mWindowInfos.isWindowObscuredAtPoint(windowHandle, x, y)) {
                 targetFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED;
-            } else if (isWindowObscuredLocked(windowHandle)) {
+            } else if (mWindowInfos.isWindowObscured(windowHandle)) {
                 targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
             }
 
@@ -2518,7 +2532,8 @@
                 if (isDownOrPointerDown && targetFlags.test(InputTarget::Flags::FOREGROUND) &&
                     windowHandle->getInfo()->inputConfig.test(
                             gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
-                    sp<WindowInfoHandle> wallpaper = findWallpaperWindowBelow(windowHandle);
+                    sp<WindowInfoHandle> wallpaper =
+                            mWindowInfos.findWallpaperWindowBelow(windowHandle);
                     if (wallpaper != nullptr) {
                         ftl::Flags<InputTarget::Flags> wallpaperFlags =
                                 InputTarget::Flags::WINDOW_IS_OBSCURED |
@@ -2630,9 +2645,9 @@
                 if (isSplit) {
                     targetFlags |= InputTarget::Flags::SPLIT;
                 }
-                if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
+                if (mWindowInfos.isWindowObscuredAtPoint(newTouchedWindowHandle, x, y)) {
                     targetFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED;
-                } else if (isWindowObscuredLocked(newTouchedWindowHandle)) {
+                } else if (mWindowInfos.isWindowObscured(newTouchedWindowHandle)) {
                     targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
                 }
 
@@ -3085,12 +3100,12 @@
  *
  * If neither of those is true, then it means the touch can be allowed.
  */
-InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLocked(
+InputDispatcher::DispatcherWindowInfo::TouchOcclusionInfo
+InputDispatcher::DispatcherWindowInfo::computeTouchOcclusionInfo(
         const sp<WindowInfoHandle>& windowHandle, float x, float y) const {
     const WindowInfo* windowInfo = windowHandle->getInfo();
     ui::LogicalDisplayId displayId = windowInfo->displayId;
-    const std::vector<sp<WindowInfoHandle>>& windowHandles =
-            mWindowInfos.getWindowHandlesForDisplay(displayId);
+    const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesForDisplay(displayId);
     TouchOcclusionInfo info;
     info.hasBlockingOcclusion = false;
     info.obscuringOpacity = 0;
@@ -3102,12 +3117,11 @@
         }
         const WindowInfo* otherInfo = otherHandle->getInfo();
         if (canBeObscuredBy(windowHandle, otherHandle) &&
-            windowOccludesTouchAt(*otherInfo, displayId, x, y,
-                                  mWindowInfos.getDisplayTransform(displayId)) &&
+            windowOccludesTouchAt(*otherInfo, displayId, x, y, getDisplayTransform(displayId)) &&
             !haveSameApplicationToken(windowInfo, otherInfo)) {
             if (DEBUG_TOUCH_OCCLUSION) {
                 info.debugInfo.push_back(
-                        dumpWindowForTouchOcclusion(otherInfo, /*isTouchedWindow=*/false));
+                        dumpWindowForTouchOcclusion(*otherInfo, /*isTouchedWindow=*/false));
             }
             // canBeObscuredBy() has returned true above, which means this window is untrusted, so
             // we perform the checks below to see if the touch can be propagated or not based on the
@@ -3135,28 +3149,14 @@
         }
     }
     if (DEBUG_TOUCH_OCCLUSION) {
-        info.debugInfo.push_back(dumpWindowForTouchOcclusion(windowInfo, /*isTouchedWindow=*/true));
+        info.debugInfo.push_back(
+                dumpWindowForTouchOcclusion(*windowInfo, /*isTouchedWindow=*/true));
     }
     return info;
 }
 
-std::string InputDispatcher::dumpWindowForTouchOcclusion(const WindowInfo* info,
-                                                         bool isTouchedWindow) const {
-    return StringPrintf(INDENT2 "* %spackage=%s/%s, id=%" PRId32 ", mode=%s, alpha=%.2f, "
-                                "frame=[%" PRId32 ",%" PRId32 "][%" PRId32 ",%" PRId32
-                                "], touchableRegion=%s, window={%s}, inputConfig={%s}, "
-                                "hasToken=%s, applicationInfo.name=%s, applicationInfo.token=%s\n",
-                        isTouchedWindow ? "[TOUCHED] " : "", info->packageName.c_str(),
-                        info->ownerUid.toString().c_str(), info->id,
-                        toString(info->touchOcclusionMode).c_str(), info->alpha, info->frame.left,
-                        info->frame.top, info->frame.right, info->frame.bottom,
-                        dumpRegion(info->touchableRegion).c_str(), info->name.c_str(),
-                        info->inputConfig.string().c_str(), toString(info->token != nullptr),
-                        info->applicationInfo.name.c_str(),
-                        binderToString(info->applicationInfo.token).c_str());
-}
-
-bool InputDispatcher::isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const {
+bool InputDispatcher::DispatcherWindowInfo::isTouchTrusted(
+        const TouchOcclusionInfo& occlusionInfo) const {
     if (occlusionInfo.hasBlockingOcclusion) {
         ALOGW("Untrusted touch due to occlusion by %s/%s", occlusionInfo.obscuringPackage.c_str(),
               occlusionInfo.obscuringUid.toString().c_str());
@@ -3172,29 +3172,27 @@
     return true;
 }
 
-bool InputDispatcher::isWindowObscuredAtPointLocked(const sp<WindowInfoHandle>& windowHandle,
-                                                    float x, float y) const {
+bool InputDispatcher::DispatcherWindowInfo::isWindowObscuredAtPoint(
+        const sp<WindowInfoHandle>& windowHandle, float x, float y) const {
     ui::LogicalDisplayId displayId = windowHandle->getInfo()->displayId;
-    const std::vector<sp<WindowInfoHandle>>& windowHandles =
-            mWindowInfos.getWindowHandlesForDisplay(displayId);
+    const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesForDisplay(displayId);
     for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
         if (windowHandle == otherHandle) {
             break; // All future windows are below us. Exit early.
         }
         const WindowInfo* otherInfo = otherHandle->getInfo();
         if (canBeObscuredBy(windowHandle, otherHandle) &&
-            windowOccludesTouchAt(*otherInfo, displayId, x, y,
-                                  mWindowInfos.getDisplayTransform(displayId))) {
+            windowOccludesTouchAt(*otherInfo, displayId, x, y, getDisplayTransform(displayId))) {
             return true;
         }
     }
     return false;
 }
 
-bool InputDispatcher::isWindowObscuredLocked(const sp<WindowInfoHandle>& windowHandle) const {
+bool InputDispatcher::DispatcherWindowInfo::isWindowObscured(
+        const sp<WindowInfoHandle>& windowHandle) const {
     ui::LogicalDisplayId displayId = windowHandle->getInfo()->displayId;
-    const std::vector<sp<WindowInfoHandle>>& windowHandles =
-            mWindowInfos.getWindowHandlesForDisplay(displayId);
+    const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesForDisplay(displayId);
     const WindowInfo* windowInfo = windowHandle->getInfo();
     for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
         if (windowHandle == otherHandle) {
@@ -4858,6 +4856,10 @@
                 flags |= AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
             }
 
+            if (policyFlags & POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL) {
+                flags |= AMOTION_EVENT_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL;
+            }
+
             mLock.lock();
 
             if (shouldRejectInjectedMotionLocked(motionEvent, resolvedDeviceId, displayId,
@@ -5264,8 +5266,9 @@
     return dump;
 }
 
-bool InputDispatcher::canWindowReceiveMotionLocked(const sp<WindowInfoHandle>& window,
-                                                   const MotionEntry& motionEntry) const {
+bool InputDispatcher::canWindowReceiveMotionLocked(
+        const sp<android::gui::WindowInfoHandle>& window,
+        const android::inputdispatcher::MotionEntry& motionEntry) const {
     const WindowInfo& info = *window->getInfo();
 
     // Skip spy window targets that are not valid for targeted injection.
@@ -5298,8 +5301,9 @@
 
     // Drop events that can't be trusted due to occlusion
     const auto [x, y] = resolveTouchedPosition(motionEntry);
-    TouchOcclusionInfo occlusionInfo = computeTouchOcclusionInfoLocked(window, x, y);
-    if (!isTouchTrustedLocked(occlusionInfo)) {
+    DispatcherWindowInfo::TouchOcclusionInfo occlusionInfo =
+            mWindowInfos.computeTouchOcclusionInfo(window, x, y);
+    if (!mWindowInfos.isTouchTrusted(occlusionInfo)) {
         if (DEBUG_TOUCH_OCCLUSION) {
             ALOGD("Stack of obscuring windows during untrusted touch (%.1f, %.1f):", x, y);
             for (const auto& log : occlusionInfo.debugInfo) {
@@ -5743,13 +5747,8 @@
 }
 
 void InputDispatcher::setMaximumObscuringOpacityForTouch(float opacity) {
-    if (opacity < 0 || opacity > 1) {
-        LOG_ALWAYS_FATAL("Maximum obscuring opacity for touch should be >= 0 and <= 1");
-        return;
-    }
-
     std::scoped_lock lock(mLock);
-    mMaximumObscuringOpacityForTouch = opacity;
+    mWindowInfos.setMaximumObscuringOpacityForTouch(opacity);
 }
 
 std::tuple<const TouchState*, const TouchedWindow*, ui::LogicalDisplayId>
@@ -7043,7 +7042,7 @@
     if (windowHandle->getInfo()->inputConfig.test(WindowInfo::InputConfig::DROP_INPUT) ||
         (windowHandle->getInfo()->inputConfig.test(
                  WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED) &&
-         isWindowObscuredLocked(windowHandle))) {
+         mWindowInfos.isWindowObscured(windowHandle))) {
         ALOGW("Dropping %s event targeting %s as requested by the input configuration {%s} on "
               "display %s.",
               ftl::enum_string(entry.type).c_str(), windowHandle->getName().c_str(),
@@ -7096,7 +7095,7 @@
     const sp<WindowInfoHandle> oldWallpaper =
             oldHasWallpaper ? state.getWallpaperWindow(deviceId) : nullptr;
     const sp<WindowInfoHandle> newWallpaper =
-            newHasWallpaper ? findWallpaperWindowBelow(newWindowHandle) : nullptr;
+            newHasWallpaper ? mWindowInfos.findWallpaperWindowBelow(newWindowHandle) : nullptr;
     if (oldWallpaper == newWallpaper) {
         return;
     }
@@ -7133,7 +7132,7 @@
     const sp<WindowInfoHandle> oldWallpaper =
             oldHasWallpaper ? state.getWallpaperWindow(deviceId) : nullptr;
     const sp<WindowInfoHandle> newWallpaper =
-            newHasWallpaper ? findWallpaperWindowBelow(toWindowHandle) : nullptr;
+            newHasWallpaper ? mWindowInfos.findWallpaperWindowBelow(toWindowHandle) : nullptr;
     if (oldWallpaper == newWallpaper) {
         return;
     }
@@ -7165,10 +7164,10 @@
     }
 }
 
-sp<WindowInfoHandle> InputDispatcher::findWallpaperWindowBelow(
+sp<WindowInfoHandle> InputDispatcher::DispatcherWindowInfo::findWallpaperWindowBelow(
         const sp<WindowInfoHandle>& windowHandle) const {
     const std::vector<sp<WindowInfoHandle>>& windowHandles =
-            mWindowInfos.getWindowHandlesForDisplay(windowHandle->getInfo()->displayId);
+            getWindowHandlesForDisplay(windowHandle->getInfo()->displayId);
     bool foundWindow = false;
     for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
         if (!foundWindow && otherHandle != windowHandle) {
@@ -7349,4 +7348,11 @@
     return dump;
 }
 
+void InputDispatcher::DispatcherWindowInfo::setMaximumObscuringOpacityForTouch(float opacity) {
+    if (opacity < 0 || opacity > 1) {
+        LOG_ALWAYS_FATAL("Maximum obscuring opacity for touch should be >= 0 and <= 1");
+    }
+    mMaximumObscuringOpacityForTouch = opacity;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 7bfa738..415f4c8 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -224,6 +224,132 @@
     /** Stores the latest user-activity poke event times per user activity types. */
     std::array<nsecs_t, USER_ACTIVITY_EVENT_LAST + 1> mLastUserActivityTimes GUARDED_BY(mLock);
 
+    template <typename T>
+    struct StrongPointerHash {
+        std::size_t operator()(const sp<T>& b) const { return std::hash<T*>{}(b.get()); }
+    };
+
+    class ConnectionManager {
+    public:
+        ConnectionManager(const sp<Looper>& lopper);
+        ~ConnectionManager();
+
+        std::shared_ptr<Connection> getConnection(const sp<IBinder>& inputConnectionToken) const;
+
+        // Find a monitor pid by the provided token.
+        std::optional<gui::Pid> findMonitorPidByToken(const sp<IBinder>& token) const;
+        void forEachGlobalMonitorConnection(
+                std::function<void(const std::shared_ptr<Connection>&)> f) const;
+        void forEachGlobalMonitorConnection(
+                ui::LogicalDisplayId displayId,
+                std::function<void(const std::shared_ptr<Connection>&)> f) const;
+
+        void createGlobalInputMonitor(ui::LogicalDisplayId displayId,
+                                      std::unique_ptr<InputChannel>&& inputChannel,
+                                      const IdGenerator& idGenerator, gui::Pid pid,
+                                      std::function<int(int)> callback);
+
+        status_t removeConnection(const std::shared_ptr<Connection>& connection);
+
+        void createConnection(std::unique_ptr<InputChannel>&& inputChannel,
+                              const IdGenerator& idGenerator, std::function<int(int)> callback);
+
+        std::string dump(nsecs_t currentTime) const;
+
+    private:
+        const sp<Looper> mLooper;
+
+        // All registered connections mapped by input channel token.
+        std::unordered_map<sp<IBinder>, std::shared_ptr<Connection>, StrongPointerHash<IBinder>>
+                mConnectionsByToken;
+
+        // Input channels that will receive a copy of all input events sent to the provided display.
+        std::unordered_map<ui::LogicalDisplayId, std::vector<Monitor>> mGlobalMonitorsByDisplay;
+
+        void removeMonitorChannel(const sp<IBinder>& connectionToken);
+    };
+
+    ConnectionManager mConnectionManager GUARDED_BY(mLock);
+
+    class DispatcherWindowInfo {
+    public:
+        struct TouchOcclusionInfo {
+            bool hasBlockingOcclusion;
+            float obscuringOpacity;
+            std::string obscuringPackage;
+            gui::Uid obscuringUid = gui::Uid::INVALID;
+            std::vector<std::string> debugInfo;
+        };
+
+        void setWindowHandlesForDisplay(
+                ui::LogicalDisplayId displayId,
+                std::vector<sp<android::gui::WindowInfoHandle>>&& windowHandles);
+
+        void setDisplayInfos(const std::vector<android::gui::DisplayInfo>& displayInfos);
+
+        void removeDisplay(ui::LogicalDisplayId displayId);
+
+        void setMaximumObscuringOpacityForTouch(float opacity);
+
+        // Get a reference to window handles by display, return an empty vector if not found.
+        const std::vector<sp<android::gui::WindowInfoHandle>>& getWindowHandlesForDisplay(
+                ui::LogicalDisplayId displayId) const;
+
+        void forEachWindowHandle(
+                std::function<void(const sp<android::gui::WindowInfoHandle>&)> f) const;
+
+        void forEachDisplayId(std::function<void(ui::LogicalDisplayId)> f) const;
+
+        // Get the transform for display, returns Identity-transform if display is missing.
+        ui::Transform getDisplayTransform(ui::LogicalDisplayId displayId) const;
+
+        // Get the raw transform to use for motion events going to the given window.
+        ui::Transform getRawTransform(const android::gui::WindowInfo&) const;
+
+        // Lookup for WindowInfoHandle from token and optionally a display-id. In cases where
+        // display-id is not provided lookup is done for all displays.
+        sp<android::gui::WindowInfoHandle> findWindowHandle(
+                const sp<IBinder>& windowHandleToken,
+                std::optional<ui::LogicalDisplayId> displayId = {}) const;
+
+        bool isWindowPresent(const sp<android::gui::WindowInfoHandle>& windowHandle) const;
+
+        // Returns the touched window at the given location, excluding the ignoreWindow if provided.
+        sp<android::gui::WindowInfoHandle> findTouchedWindowAt(
+                ui::LogicalDisplayId displayId, float x, float y, bool isStylus = false,
+                const sp<android::gui::WindowInfoHandle> ignoreWindow = nullptr) const;
+
+        std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAt(
+                ui::LogicalDisplayId displayId, float x, float y, bool isStylus, DeviceId deviceId,
+                const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay)
+                const;
+
+        TouchOcclusionInfo computeTouchOcclusionInfo(
+                const sp<android::gui::WindowInfoHandle>& windowHandle, float x, float y) const;
+
+        bool isWindowObscured(const sp<android::gui::WindowInfoHandle>& windowHandle) const;
+
+        bool isWindowObscuredAtPoint(const sp<android::gui::WindowInfoHandle>& windowHandle,
+                                     float x, float y) const;
+
+        sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
+                const sp<android::gui::WindowInfoHandle>& windowHandle) const;
+
+        bool isTouchTrusted(const TouchOcclusionInfo& occlusionInfo) const;
+
+        std::string dumpDisplayAndWindowInfo() const;
+
+    private:
+        std::unordered_map<ui::LogicalDisplayId /*displayId*/,
+                           std::vector<sp<android::gui::WindowInfoHandle>>>
+                mWindowHandlesByDisplay;
+        std::unordered_map<ui::LogicalDisplayId /*displayId*/, android::gui::DisplayInfo>
+                mDisplayInfos;
+        float mMaximumObscuringOpacityForTouch{1.0f};
+    };
+
+    DispatcherWindowInfo mWindowInfos GUARDED_BY(mLock);
+
     // With each iteration, InputDispatcher nominally processes one queued event,
     // a timeout, or a response from an input consumer.
     // This method should only be called on the input dispatcher's own thread.
@@ -262,11 +388,6 @@
 
     status_t pilferPointersLocked(const sp<IBinder>& token) REQUIRES(mLock);
 
-    template <typename T>
-    struct StrongPointerHash {
-        std::size_t operator()(const sp<T>& b) const { return std::hash<T*>{}(b.get()); }
-    };
-
     const HmacKeyManager mHmacKeyManager;
     const std::array<uint8_t, 32> getSignature(const MotionEntry& motionEntry,
                                                const DispatchEntry& dispatchEntry) const;
@@ -326,7 +447,6 @@
     bool mDispatchEnabled GUARDED_BY(mLock);
     bool mDispatchFrozen GUARDED_BY(mLock);
     bool mInputFilterEnabled GUARDED_BY(mLock);
-    float mMaximumObscuringOpacityForTouch GUARDED_BY(mLock);
 
     // This map is not really needed, but it helps a lot with debugging (dumpsys input).
     // In the java layer, touch mode states are spread across multiple DisplayContent objects,
@@ -344,103 +464,6 @@
     };
     sp<gui::WindowInfosListener> mWindowInfoListener;
 
-    class DispatcherWindowInfo {
-    public:
-        void setWindowHandlesForDisplay(
-                ui::LogicalDisplayId displayId,
-                std::vector<sp<android::gui::WindowInfoHandle>>&& windowHandles);
-
-        void setDisplayInfos(const std::vector<android::gui::DisplayInfo>& displayInfos);
-
-        void removeDisplay(ui::LogicalDisplayId displayId);
-
-        // Get a reference to window handles by display, return an empty vector if not found.
-        const std::vector<sp<android::gui::WindowInfoHandle>>& getWindowHandlesForDisplay(
-                ui::LogicalDisplayId displayId) const;
-
-        void forEachWindowHandle(
-                std::function<void(const sp<android::gui::WindowInfoHandle>&)> f) const;
-
-        void forEachDisplayId(std::function<void(ui::LogicalDisplayId)> f) const;
-
-        // Get the transform for display, returns Identity-transform if display is missing.
-        ui::Transform getDisplayTransform(ui::LogicalDisplayId displayId) const;
-
-        // Get the raw transform to use for motion events going to the given window.
-        ui::Transform getRawTransform(const android::gui::WindowInfo&) const;
-
-        // Lookup for WindowInfoHandle from token and optionally a display-id. In cases where
-        // display-id is not provided lookup is done for all displays.
-        sp<android::gui::WindowInfoHandle> findWindowHandle(
-                const sp<IBinder>& windowHandleToken,
-                std::optional<ui::LogicalDisplayId> displayId = {}) const;
-
-        bool isWindowPresent(const sp<android::gui::WindowInfoHandle>& windowHandle) const;
-
-        // Returns the touched window at the given location, excluding the ignoreWindow if provided.
-        sp<android::gui::WindowInfoHandle> findTouchedWindowAt(
-                ui::LogicalDisplayId displayId, float x, float y, bool isStylus = false,
-                const sp<android::gui::WindowInfoHandle> ignoreWindow = nullptr) const;
-
-        std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAt(
-                ui::LogicalDisplayId displayId, float x, float y, bool isStylus, DeviceId deviceId,
-                const std::unordered_map<ui::LogicalDisplayId, TouchState>& touchStatesByDisplay)
-                const;
-
-        std::string dumpDisplayAndWindowInfo() const;
-
-    private:
-        std::unordered_map<ui::LogicalDisplayId /*displayId*/,
-                           std::vector<sp<android::gui::WindowInfoHandle>>>
-                mWindowHandlesByDisplay;
-        std::unordered_map<ui::LogicalDisplayId /*displayId*/, android::gui::DisplayInfo>
-                mDisplayInfos;
-    };
-
-    DispatcherWindowInfo mWindowInfos GUARDED_BY(mLock);
-
-    class ConnectionManager {
-    public:
-        ConnectionManager(const sp<Looper>& lopper);
-        ~ConnectionManager();
-
-        std::shared_ptr<Connection> getConnection(const sp<IBinder>& inputConnectionToken) const;
-
-        // Find a monitor pid by the provided token.
-        std::optional<gui::Pid> findMonitorPidByToken(const sp<IBinder>& token) const;
-        void forEachGlobalMonitorConnection(
-                std::function<void(const std::shared_ptr<Connection>&)> f) const;
-        void forEachGlobalMonitorConnection(
-                ui::LogicalDisplayId displayId,
-                std::function<void(const std::shared_ptr<Connection>&)> f) const;
-
-        void createGlobalInputMonitor(ui::LogicalDisplayId displayId,
-                                      std::unique_ptr<InputChannel>&& inputChannel,
-                                      const IdGenerator& idGenerator, gui::Pid pid,
-                                      std::function<int(int)> callback);
-
-        status_t removeConnection(const std::shared_ptr<Connection>& connection);
-
-        void createConnection(std::unique_ptr<InputChannel>&& inputChannel,
-                              const IdGenerator& idGenerator, std::function<int(int)> callback);
-
-        std::string dump(nsecs_t currentTime) const;
-
-    private:
-        const sp<Looper> mLooper;
-
-        // All registered connections mapped by input channel token.
-        std::unordered_map<sp<IBinder>, std::shared_ptr<Connection>, StrongPointerHash<IBinder>>
-                mConnectionsByToken;
-
-        // Input channels that will receive a copy of all input events sent to the provided display.
-        std::unordered_map<ui::LogicalDisplayId, std::vector<Monitor>> mGlobalMonitorsByDisplay;
-
-        void removeMonitorChannel(const sp<IBinder>& connectionToken);
-    };
-
-    ConnectionManager mConnectionManager GUARDED_BY(mLock);
-
     void setInputWindowsLocked(
             const std::vector<sp<android::gui::WindowInfoHandle>>& inputWindowHandles,
             ui::LogicalDisplayId displayId) REQUIRES(mLock);
@@ -626,24 +649,6 @@
     void addDragEventLocked(const MotionEntry& entry) REQUIRES(mLock);
     void finishDragAndDrop(ui::LogicalDisplayId displayId, float x, float y) REQUIRES(mLock);
 
-    struct TouchOcclusionInfo {
-        bool hasBlockingOcclusion;
-        float obscuringOpacity;
-        std::string obscuringPackage;
-        gui::Uid obscuringUid = gui::Uid::INVALID;
-        std::vector<std::string> debugInfo;
-    };
-
-    TouchOcclusionInfo computeTouchOcclusionInfoLocked(
-            const sp<android::gui::WindowInfoHandle>& windowHandle, float x, float y) const
-            REQUIRES(mLock);
-    bool isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const REQUIRES(mLock);
-    bool isWindowObscuredAtPointLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                                       float x, float y) const REQUIRES(mLock);
-    bool isWindowObscuredLocked(const sp<android::gui::WindowInfoHandle>& windowHandle) const
-            REQUIRES(mLock);
-    std::string dumpWindowForTouchOcclusion(const android::gui::WindowInfo* info,
-                                            bool isTouchWindow) const;
     std::string getApplicationWindowLabel(const InputApplicationHandle* applicationHandle,
                                           const sp<android::gui::WindowInfoHandle>& windowHandle);
 
@@ -796,9 +801,6 @@
                                 const std::unique_ptr<trace::EventTrackerInterface>& traceTracker)
             REQUIRES(mLock);
 
-    sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
-            const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
-
     /** Stores the value of the input flag for per device input latency metrics. */
     const bool mPerDeviceInputLatencyMetricsFlag =
             com::android::input::flags::enable_per_device_input_latency_metrics();
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
deleted file mode 100644
index 535c7ae..0000000
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * 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 "AndroidInputEventProtoConverter.h"
-
-#include <android/input.h>
-#include <android-base/logging.h>
-#include <input/Input.h>
-#include <perfetto/trace/android/android_input_event.pbzero.h>
-
-namespace android::inputdispatcher::trace {
-
-namespace {
-
-using namespace ftl::flag_operators;
-
-// The trace config to use for maximal tracing.
-const impl::TraceConfig CONFIG_TRACE_ALL{
-        .flags = impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS |
-                impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH,
-        .rules = {impl::TraceRule{.level = impl::TraceLevel::TRACE_LEVEL_COMPLETE,
-                                  .matchAllPackages = {},
-                                  .matchAnyPackages = {},
-                                  .matchSecure{},
-                                  .matchImeConnectionActive = {}}},
-};
-
-} // namespace
-
-void AndroidInputEventProtoConverter::toProtoMotionEvent(const TracedMotionEvent& event,
-                                                         proto::AndroidMotionEvent& outProto,
-                                                         bool isRedacted) {
-    outProto.set_event_id(event.id);
-    outProto.set_event_time_nanos(event.eventTime);
-    outProto.set_down_time_nanos(event.downTime);
-    outProto.set_source(event.source);
-    outProto.set_action(event.action);
-    outProto.set_device_id(event.deviceId);
-    outProto.set_display_id(event.displayId.val());
-    outProto.set_classification(static_cast<int32_t>(event.classification));
-    outProto.set_flags(event.flags);
-    outProto.set_policy_flags(event.policyFlags);
-    outProto.set_button_state(event.buttonState);
-    outProto.set_action_button(event.actionButton);
-
-    if (!isRedacted) {
-        outProto.set_cursor_position_x(event.xCursorPosition);
-        outProto.set_cursor_position_y(event.yCursorPosition);
-        outProto.set_meta_state(event.metaState);
-        outProto.set_precision_x(event.xPrecision);
-        outProto.set_precision_y(event.yPrecision);
-    }
-
-    for (uint32_t i = 0; i < event.pointerProperties.size(); i++) {
-        auto* pointer = outProto.add_pointer();
-
-        const auto& props = event.pointerProperties[i];
-        pointer->set_pointer_id(props.id);
-        pointer->set_tool_type(static_cast<int32_t>(props.toolType));
-
-        const auto& coords = event.pointerCoords[i];
-        auto bits = BitSet64(coords.bits);
-        if (isFromSource(event.source, AINPUT_SOURCE_CLASS_POINTER)) {
-            // Always include the X and Y axes for pointer events, since the
-            // bits will not be marked if the value is 0.
-            bits.markBit(AMOTION_EVENT_AXIS_X);
-            bits.markBit(AMOTION_EVENT_AXIS_Y);
-        }
-        for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
-            const auto axis = bits.clearFirstMarkedBit();
-            auto axisEntry = pointer->add_axis_value();
-            axisEntry->set_axis(axis);
-
-            if (!isRedacted) {
-                axisEntry->set_value(coords.values[axisIndex]);
-            }
-        }
-    }
-}
-
-void AndroidInputEventProtoConverter::toProtoKeyEvent(const TracedKeyEvent& event,
-                                                      proto::AndroidKeyEvent& outProto,
-                                                      bool isRedacted) {
-    outProto.set_event_id(event.id);
-    outProto.set_event_time_nanos(event.eventTime);
-    outProto.set_down_time_nanos(event.downTime);
-    outProto.set_source(event.source);
-    outProto.set_action(event.action);
-    outProto.set_device_id(event.deviceId);
-    outProto.set_display_id(event.displayId.val());
-    outProto.set_repeat_count(event.repeatCount);
-    outProto.set_flags(event.flags);
-    outProto.set_policy_flags(event.policyFlags);
-
-    if (!isRedacted) {
-        outProto.set_key_code(event.keyCode);
-        outProto.set_scan_code(event.scanCode);
-        outProto.set_meta_state(event.metaState);
-    }
-}
-
-void AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(
-        const WindowDispatchArgs& args, proto::AndroidWindowInputDispatchEvent& outProto,
-        bool isRedacted) {
-    std::visit([&](auto entry) { outProto.set_event_id(entry.id); }, args.eventEntry);
-    outProto.set_vsync_id(args.vsyncId);
-    outProto.set_window_id(args.windowId);
-    outProto.set_resolved_flags(args.resolvedFlags);
-
-    if (isRedacted) {
-        return;
-    }
-    if (auto* motion = std::get_if<TracedMotionEvent>(&args.eventEntry); motion != nullptr) {
-        for (size_t i = 0; i < motion->pointerProperties.size(); i++) {
-            auto* pointerProto = outProto.add_dispatched_pointer();
-            pointerProto->set_pointer_id(motion->pointerProperties[i].id);
-            const auto& coords = motion->pointerCoords[i];
-            const auto rawXY =
-                    MotionEvent::calculateTransformedXY(motion->source, args.rawTransform,
-                                                        coords.getXYValue());
-            if (coords.getXYValue() != rawXY) {
-                // These values are only traced if they were modified by the raw transform
-                // to save space. Trace consumers should be aware of this optimization.
-                pointerProto->set_x_in_display(rawXY.x);
-                pointerProto->set_y_in_display(rawXY.y);
-            }
-
-            const auto coordsInWindow =
-                    MotionEvent::calculateTransformedCoords(motion->source, motion->flags,
-                                                            args.transform, coords);
-            auto bits = BitSet64(coords.bits);
-            for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
-                const uint32_t axis = bits.clearFirstMarkedBit();
-                const float axisValueInWindow = coordsInWindow.values[axisIndex];
-                // Only values that are modified by the window transform are traced.
-                if (coords.values[axisIndex] != axisValueInWindow) {
-                    auto* axisEntry = pointerProto->add_axis_value_in_window();
-                    axisEntry->set_axis(axis);
-                    axisEntry->set_value(axisValueInWindow);
-                }
-            }
-        }
-    }
-}
-
-impl::TraceConfig AndroidInputEventProtoConverter::parseConfig(
-        proto::AndroidInputEventConfig::Decoder& protoConfig) {
-    if (protoConfig.has_mode() &&
-        protoConfig.mode() == proto::AndroidInputEventConfig::TRACE_MODE_TRACE_ALL) {
-        // User has requested the preset for maximal tracing
-        return CONFIG_TRACE_ALL;
-    }
-
-    impl::TraceConfig config;
-
-    // Parse trace flags
-    if (protoConfig.has_trace_dispatcher_input_events() &&
-        protoConfig.trace_dispatcher_input_events()) {
-        config.flags |= impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS;
-    }
-    if (protoConfig.has_trace_dispatcher_window_dispatch() &&
-        protoConfig.trace_dispatcher_window_dispatch()) {
-        config.flags |= impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH;
-    }
-
-    // Parse trace rules
-    auto rulesIt = protoConfig.rules();
-    while (rulesIt) {
-        proto::AndroidInputEventConfig::TraceRule::Decoder protoRule{rulesIt->as_bytes()};
-        config.rules.emplace_back();
-        auto& rule = config.rules.back();
-
-        rule.level = protoRule.has_trace_level()
-                ? static_cast<impl::TraceLevel>(protoRule.trace_level())
-                : impl::TraceLevel::TRACE_LEVEL_NONE;
-
-        if (protoRule.has_match_all_packages()) {
-            auto pkgIt = protoRule.match_all_packages();
-            while (pkgIt) {
-                rule.matchAllPackages.emplace_back(pkgIt->as_std_string());
-                pkgIt++;
-            }
-        }
-
-        if (protoRule.has_match_any_packages()) {
-            auto pkgIt = protoRule.match_any_packages();
-            while (pkgIt) {
-                rule.matchAnyPackages.emplace_back(pkgIt->as_std_string());
-                pkgIt++;
-            }
-        }
-
-        if (protoRule.has_match_secure()) {
-            rule.matchSecure = protoRule.match_secure();
-        }
-
-        if (protoRule.has_match_ime_connection_active()) {
-            rule.matchImeConnectionActive = protoRule.match_ime_connection_active();
-        }
-
-        rulesIt++;
-    }
-
-    return config;
-}
-
-} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
index 887913f..c19d278 100644
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
@@ -26,20 +26,214 @@
 
 namespace android::inputdispatcher::trace {
 
+namespace internal {
+
+using namespace ftl::flag_operators;
+
+// The trace config to use for maximal tracing.
+const impl::TraceConfig CONFIG_TRACE_ALL{
+        .flags = impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS |
+                impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH,
+        .rules = {impl::TraceRule{.level = impl::TraceLevel::TRACE_LEVEL_COMPLETE,
+                                  .matchAllPackages = {},
+                                  .matchAnyPackages = {},
+                                  .matchSecure{},
+                                  .matchImeConnectionActive = {}}},
+};
+
+template <typename Pointer>
+void writeAxisValue(Pointer* pointer, int32_t axis, float value, bool isRedacted) {
+    auto* axisEntry = pointer->add_axis_value();
+    axisEntry->set_axis(axis);
+
+    if (!isRedacted) {
+        axisEntry->set_value(value);
+    }
+}
+
+} // namespace internal
+
 /**
  * Write traced events into Perfetto protos.
+ *
+ * This class is templated so that the logic can be tested while substituting the proto classes
+ * auto-generated by Perfetto's pbzero library with mock implementations.
  */
+template <typename ProtoMotion, typename ProtoKey, typename ProtoDispatch,
+          typename ProtoConfigDecoder>
 class AndroidInputEventProtoConverter {
 public:
-    static void toProtoMotionEvent(const TracedMotionEvent& event,
-                                   proto::AndroidMotionEvent& outProto, bool isRedacted);
-    static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto,
-                                bool isRedacted);
-    static void toProtoWindowDispatchEvent(const WindowDispatchArgs&,
-                                           proto::AndroidWindowInputDispatchEvent& outProto,
-                                           bool isRedacted);
+    static void toProtoMotionEvent(const TracedMotionEvent& event, ProtoMotion& outProto,
+                                   bool isRedacted) {
+        outProto.set_event_id(event.id);
+        outProto.set_event_time_nanos(event.eventTime);
+        outProto.set_down_time_nanos(event.downTime);
+        outProto.set_source(event.source);
+        outProto.set_action(event.action);
+        outProto.set_device_id(event.deviceId);
+        outProto.set_display_id(event.displayId.val());
+        outProto.set_classification(static_cast<int32_t>(event.classification));
+        outProto.set_flags(event.flags);
+        outProto.set_policy_flags(event.policyFlags);
+        outProto.set_button_state(event.buttonState);
+        outProto.set_action_button(event.actionButton);
 
-    static impl::TraceConfig parseConfig(proto::AndroidInputEventConfig::Decoder& protoConfig);
+        if (!isRedacted) {
+            outProto.set_cursor_position_x(event.xCursorPosition);
+            outProto.set_cursor_position_y(event.yCursorPosition);
+            outProto.set_meta_state(event.metaState);
+            outProto.set_precision_x(event.xPrecision);
+            outProto.set_precision_y(event.yPrecision);
+        }
+
+        for (uint32_t i = 0; i < event.pointerProperties.size(); i++) {
+            auto* pointer = outProto.add_pointer();
+
+            const auto& props = event.pointerProperties[i];
+            pointer->set_pointer_id(props.id);
+            pointer->set_tool_type(static_cast<int32_t>(props.toolType));
+
+            const auto& coords = event.pointerCoords[i];
+            auto bits = BitSet64(coords.bits);
+
+            if (isFromSource(event.source, AINPUT_SOURCE_CLASS_POINTER)) {
+                // Always include the X and Y axes for pointer events, since the
+                // bits will not be marked if the value is 0.
+                for (const auto axis : {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y}) {
+                    if (!bits.hasBit(axis)) {
+                        internal::writeAxisValue(pointer, axis, 0.0f, isRedacted);
+                    }
+                }
+            }
+
+            for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
+                const auto axis = bits.clearFirstMarkedBit();
+                internal::writeAxisValue(pointer, axis, coords.values[axisIndex], isRedacted);
+            }
+        }
+    }
+
+    static void toProtoKeyEvent(const TracedKeyEvent& event, ProtoKey& outProto, bool isRedacted) {
+        outProto.set_event_id(event.id);
+        outProto.set_event_time_nanos(event.eventTime);
+        outProto.set_down_time_nanos(event.downTime);
+        outProto.set_source(event.source);
+        outProto.set_action(event.action);
+        outProto.set_device_id(event.deviceId);
+        outProto.set_display_id(event.displayId.val());
+        outProto.set_repeat_count(event.repeatCount);
+        outProto.set_flags(event.flags);
+        outProto.set_policy_flags(event.policyFlags);
+
+        if (!isRedacted) {
+            outProto.set_key_code(event.keyCode);
+            outProto.set_scan_code(event.scanCode);
+            outProto.set_meta_state(event.metaState);
+        }
+    }
+
+    static void toProtoWindowDispatchEvent(const WindowDispatchArgs& args, ProtoDispatch& outProto,
+                                           bool isRedacted) {
+        std::visit([&](auto entry) { outProto.set_event_id(entry.id); }, args.eventEntry);
+        outProto.set_vsync_id(args.vsyncId);
+        outProto.set_window_id(args.windowId);
+        outProto.set_resolved_flags(args.resolvedFlags);
+
+        if (isRedacted) {
+            return;
+        }
+        if (auto* motion = std::get_if<TracedMotionEvent>(&args.eventEntry); motion != nullptr) {
+            for (size_t i = 0; i < motion->pointerProperties.size(); i++) {
+                auto* pointerProto = outProto.add_dispatched_pointer();
+                pointerProto->set_pointer_id(motion->pointerProperties[i].id);
+                const auto& coords = motion->pointerCoords[i];
+                const auto rawXY =
+                        MotionEvent::calculateTransformedXY(motion->source, args.rawTransform,
+                                                            coords.getXYValue());
+                if (coords.getXYValue() != rawXY) {
+                    // These values are only traced if they were modified by the raw transform
+                    // to save space. Trace consumers should be aware of this optimization.
+                    pointerProto->set_x_in_display(rawXY.x);
+                    pointerProto->set_y_in_display(rawXY.y);
+                }
+
+                const auto coordsInWindow =
+                        MotionEvent::calculateTransformedCoords(motion->source, motion->flags,
+                                                                args.transform, coords);
+                auto bits = BitSet64(coords.bits);
+                for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
+                    const uint32_t axis = bits.clearFirstMarkedBit();
+                    const float axisValueInWindow = coordsInWindow.values[axisIndex];
+                    // Only values that are modified by the window transform are traced.
+                    if (coords.values[axisIndex] != axisValueInWindow) {
+                        auto* axisEntry = pointerProto->add_axis_value_in_window();
+                        axisEntry->set_axis(axis);
+                        axisEntry->set_value(axisValueInWindow);
+                    }
+                }
+            }
+        }
+    }
+
+    static impl::TraceConfig parseConfig(ProtoConfigDecoder& protoConfig) {
+        if (protoConfig.has_mode() &&
+            protoConfig.mode() == proto::AndroidInputEventConfig::TRACE_MODE_TRACE_ALL) {
+            // User has requested the preset for maximal tracing
+            return internal::CONFIG_TRACE_ALL;
+        }
+
+        impl::TraceConfig config;
+
+        // Parse trace flags
+        if (protoConfig.has_trace_dispatcher_input_events() &&
+            protoConfig.trace_dispatcher_input_events()) {
+            config.flags |= impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS;
+        }
+        if (protoConfig.has_trace_dispatcher_window_dispatch() &&
+            protoConfig.trace_dispatcher_window_dispatch()) {
+            config.flags |= impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH;
+        }
+
+        // Parse trace rules
+        auto rulesIt = protoConfig.rules();
+        while (rulesIt) {
+            proto::AndroidInputEventConfig::TraceRule::Decoder protoRule{rulesIt->as_bytes()};
+            config.rules.emplace_back();
+            auto& rule = config.rules.back();
+
+            rule.level = protoRule.has_trace_level()
+                    ? static_cast<impl::TraceLevel>(protoRule.trace_level())
+                    : impl::TraceLevel::TRACE_LEVEL_NONE;
+
+            if (protoRule.has_match_all_packages()) {
+                auto pkgIt = protoRule.match_all_packages();
+                while (pkgIt) {
+                    rule.matchAllPackages.emplace_back(pkgIt->as_std_string());
+                    pkgIt++;
+                }
+            }
+
+            if (protoRule.has_match_any_packages()) {
+                auto pkgIt = protoRule.match_any_packages();
+                while (pkgIt) {
+                    rule.matchAnyPackages.emplace_back(pkgIt->as_std_string());
+                    pkgIt++;
+                }
+            }
+
+            if (protoRule.has_match_secure()) {
+                rule.matchSecure = protoRule.match_secure();
+            }
+
+            if (protoRule.has_match_ime_connection_active()) {
+                rule.matchImeConnectionActive = protoRule.match_ime_connection_active();
+            }
+
+            rulesIt++;
+        }
+
+        return config;
+    }
 };
 
 } // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
index 761d619..2ff6e1c 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
@@ -50,7 +50,7 @@
     uint32_t policyFlags;
     int32_t deviceId;
     uint32_t source;
-    ui::LogicalDisplayId displayId;
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
     int32_t action;
     int32_t keyCode;
     int32_t scanCode;
@@ -70,7 +70,7 @@
     uint32_t policyFlags;
     int32_t deviceId;
     uint32_t source;
-    ui::LogicalDisplayId displayId;
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
     int32_t action;
     int32_t actionButton;
     int32_t flags;
@@ -108,7 +108,7 @@
     TracedEvent eventEntry;
     nsecs_t deliveryTime;
     int32_t resolvedFlags;
-    gui::Uid targetUid;
+    gui::Uid targetUid = gui::Uid::INVALID;
     int64_t vsyncId;
     int32_t windowId;
     ui::Transform transform;
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
index 77b5c2e..ebcd9c9 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -34,6 +34,11 @@
 
 constexpr auto INPUT_EVENT_TRACE_DATA_SOURCE_NAME = "android.input.inputevent";
 
+using ProtoConverter =
+        AndroidInputEventProtoConverter<proto::AndroidMotionEvent, proto::AndroidKeyEvent,
+                                        proto::AndroidWindowInputDispatchEvent,
+                                        proto::AndroidInputEventConfig::Decoder>;
+
 bool isPermanentlyAllowed(gui::Uid uid) {
     switch (uid.val()) {
         case AID_SYSTEM:
@@ -85,7 +90,7 @@
     const auto rawConfig = args.config->android_input_event_config_raw();
     auto protoConfig = perfetto::protos::pbzero::AndroidInputEventConfig::Decoder{rawConfig};
 
-    mConfig = AndroidInputEventProtoConverter::parseConfig(protoConfig);
+    mConfig = ProtoConverter::parseConfig(protoConfig);
 }
 
 void PerfettoBackend::InputEventDataSource::OnStart(const InputEventDataSource::StartArgs&) {
@@ -238,7 +243,7 @@
         auto* inputEvent = winscopeExtensions->set_android_input_event();
         auto* dispatchMotion = isRedacted ? inputEvent->set_dispatcher_motion_event_redacted()
                                           : inputEvent->set_dispatcher_motion_event();
-        AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion, isRedacted);
+        ProtoConverter::toProtoMotionEvent(event, *dispatchMotion, isRedacted);
     });
 }
 
@@ -266,7 +271,7 @@
         auto* inputEvent = winscopeExtensions->set_android_input_event();
         auto* dispatchKey = isRedacted ? inputEvent->set_dispatcher_key_event_redacted()
                                        : inputEvent->set_dispatcher_key_event();
-        AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey, isRedacted);
+        ProtoConverter::toProtoKeyEvent(event, *dispatchKey, isRedacted);
     });
 }
 
@@ -295,8 +300,7 @@
         auto* dispatchEvent = isRedacted
                 ? inputEvent->set_dispatcher_window_dispatch_event_redacted()
                 : inputEvent->set_dispatcher_window_dispatch_event();
-        AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, *dispatchEvent,
-                                                                    isRedacted);
+        ProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, *dispatchEvent, isRedacted);
     });
 }
 
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 600ae52..18d47f6 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -40,14 +40,15 @@
         // defaults rather than including them as shared or static libraries. By doing so, the tests
         // will always run against the compiled version of the inputflinger code rather than the
         // version on the device.
+        "libinputdispatcher_defaults",
         "libinputflinger_base_defaults",
+        "libinputflinger_defaults",
         "libinputreader_defaults",
         "libinputreporter_defaults",
-        "libinputdispatcher_defaults",
-        "libinputflinger_defaults",
     ],
     srcs: [
         ":inputdispatcher_common_test_sources",
+        "AndroidInputEventProtoConverter_test.cpp",
         "AnrTracker_test.cpp",
         "CapturedTouchpadEventConverter_test.cpp",
         "CursorInputMapper_test.cpp",
@@ -62,16 +63,18 @@
         "HardwareStateConverter_test.cpp",
         "InputDeviceMetricsCollector_test.cpp",
         "InputDeviceMetricsSource_test.cpp",
-        "InputMapperTest.cpp",
-        "InputProcessor_test.cpp",
-        "InputProcessorConverter_test.cpp",
         "InputDispatcher_test.cpp",
+        "InputMapperTest.cpp",
+        "InputProcessorConverter_test.cpp",
+        "InputProcessor_test.cpp",
         "InputReader_test.cpp",
         "InputTraceSession.cpp",
         "InputTracingTest.cpp",
         "InstrumentedInputReader.cpp",
         "JoystickInputMapper_test.cpp",
+        "KeyboardInputMapper_test.cpp",
         "LatencyTracker_test.cpp",
+        "MultiTouchInputMapper_test.cpp",
         "MultiTouchMotionAccumulator_test.cpp",
         "NotifyArgs_test.cpp",
         "PointerChoreographer_test.cpp",
@@ -82,14 +85,12 @@
         "SlopController_test.cpp",
         "SwitchInputMapper_test.cpp",
         "SyncQueue_test.cpp",
-        "TimerProvider_test.cpp",
         "TestInputListener.cpp",
+        "TimerProvider_test.cpp",
         "TouchpadInputMapper_test.cpp",
-        "VibratorInputMapper_test.cpp",
-        "MultiTouchInputMapper_test.cpp",
-        "KeyboardInputMapper_test.cpp",
         "UinputDevice.cpp",
         "UnwantedInteractionBlocker_test.cpp",
+        "VibratorInputMapper_test.cpp",
     ],
     aidl: {
         include_dirs: [
@@ -109,7 +110,14 @@
         undefined: true,
         all_undefined: true,
         diag: {
+            cfi: true,
+            integer_overflow: true,
+            memtag_heap: true,
             undefined: true,
+            misc_undefined: [
+                "all",
+                "bounds",
+            ],
         },
     },
     static_libs: [
@@ -121,8 +129,8 @@
         unit_test: true,
     },
     test_suites: [
-        "device-tests",
         "device-platinum-tests",
+        "device-tests",
     ],
     native_coverage: false,
 }
diff --git a/services/inputflinger/tests/AndroidInputEventProtoConverter_test.cpp b/services/inputflinger/tests/AndroidInputEventProtoConverter_test.cpp
new file mode 100644
index 0000000..1fd6cee
--- /dev/null
+++ b/services/inputflinger/tests/AndroidInputEventProtoConverter_test.cpp
@@ -0,0 +1,586 @@
+/*
+ * Copyright 2025 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 "../dispatcher/trace/AndroidInputEventProtoConverter.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android::inputdispatcher::trace {
+
+namespace {
+
+using testing::Return, testing::_;
+
+class MockProtoAxisValue {
+public:
+    MOCK_METHOD(void, set_axis, (int32_t));
+    MOCK_METHOD(void, set_value, (float));
+};
+
+class MockProtoPointer {
+public:
+    MOCK_METHOD(void, set_pointer_id, (uint32_t));
+    MOCK_METHOD(void, set_tool_type, (int32_t));
+    MOCK_METHOD(MockProtoAxisValue*, add_axis_value, ());
+};
+
+class MockProtoMotion {
+public:
+    MOCK_METHOD(void, set_event_id, (uint32_t));
+    MOCK_METHOD(void, set_event_time_nanos, (int64_t));
+    MOCK_METHOD(void, set_down_time_nanos, (int64_t));
+    MOCK_METHOD(void, set_source, (uint32_t));
+    MOCK_METHOD(void, set_action, (int32_t));
+    MOCK_METHOD(void, set_device_id, (uint32_t));
+    MOCK_METHOD(void, set_display_id, (uint32_t));
+    MOCK_METHOD(void, set_classification, (int32_t));
+    MOCK_METHOD(void, set_flags, (uint32_t));
+    MOCK_METHOD(void, set_policy_flags, (uint32_t));
+    MOCK_METHOD(void, set_button_state, (uint32_t));
+    MOCK_METHOD(void, set_action_button, (uint32_t));
+    MOCK_METHOD(void, set_cursor_position_x, (float));
+    MOCK_METHOD(void, set_cursor_position_y, (float));
+    MOCK_METHOD(void, set_meta_state, (uint32_t));
+    MOCK_METHOD(void, set_precision_x, (float));
+    MOCK_METHOD(void, set_precision_y, (float));
+    MOCK_METHOD(MockProtoPointer*, add_pointer, ());
+};
+
+class MockProtoKey {
+public:
+    MOCK_METHOD(void, set_event_id, (uint32_t));
+    MOCK_METHOD(void, set_event_time_nanos, (int64_t));
+    MOCK_METHOD(void, set_down_time_nanos, (int64_t));
+    MOCK_METHOD(void, set_source, (uint32_t));
+    MOCK_METHOD(void, set_action, (int32_t));
+    MOCK_METHOD(void, set_device_id, (uint32_t));
+    MOCK_METHOD(void, set_display_id, (uint32_t));
+    MOCK_METHOD(void, set_repeat_count, (uint32_t));
+    MOCK_METHOD(void, set_flags, (uint32_t));
+    MOCK_METHOD(void, set_policy_flags, (uint32_t));
+    MOCK_METHOD(void, set_key_code, (uint32_t));
+    MOCK_METHOD(void, set_scan_code, (uint32_t));
+    MOCK_METHOD(void, set_meta_state, (uint32_t));
+};
+
+class MockProtoDispatchPointer {
+public:
+    MOCK_METHOD(void, set_pointer_id, (uint32_t));
+    MOCK_METHOD(void, set_x_in_display, (float));
+    MOCK_METHOD(void, set_y_in_display, (float));
+    MOCK_METHOD(MockProtoAxisValue*, add_axis_value_in_window, ());
+};
+
+class MockProtoDispatch {
+public:
+    MOCK_METHOD(void, set_event_id, (uint32_t));
+    MOCK_METHOD(void, set_vsync_id, (uint32_t));
+    MOCK_METHOD(void, set_window_id, (uint32_t));
+    MOCK_METHOD(void, set_resolved_flags, (uint32_t));
+    MOCK_METHOD(MockProtoDispatchPointer*, add_dispatched_pointer, ());
+};
+
+using TestProtoConverter =
+        AndroidInputEventProtoConverter<MockProtoMotion, MockProtoKey, MockProtoDispatch,
+                                        proto::AndroidInputEventConfig::Decoder>;
+
+TEST(AndroidInputEventProtoConverterTest, ToProtoMotionEvent) {
+    TracedMotionEvent event{};
+    event.id = 1;
+    event.eventTime = 2;
+    event.downTime = 3;
+    event.source = AINPUT_SOURCE_MOUSE;
+    event.action = AMOTION_EVENT_ACTION_BUTTON_PRESS;
+    event.deviceId = 4;
+    event.displayId = ui::LogicalDisplayId(5);
+    event.classification = MotionClassification::PINCH;
+    event.flags = 6;
+    event.policyFlags = 7;
+    event.buttonState = 8;
+    event.actionButton = 9;
+    event.xCursorPosition = 10.0f;
+    event.yCursorPosition = 11.0f;
+    event.metaState = 12;
+    event.xPrecision = 13.0f;
+    event.yPrecision = 14.0f;
+    event.pointerProperties.emplace_back(PointerProperties{
+            .id = 15,
+            .toolType = ToolType::MOUSE,
+    });
+    event.pointerProperties.emplace_back(PointerProperties{
+            .id = 16,
+            .toolType = ToolType::FINGER,
+    });
+    event.pointerCoords.emplace_back();
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 17.0f);
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 18.0f);
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 19.0f);
+    event.pointerCoords.emplace_back();
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 20.0f);
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 21.0f);
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 22.0f);
+
+    testing::StrictMock<MockProtoMotion> proto;
+    testing::StrictMock<MockProtoPointer> pointer1;
+    testing::StrictMock<MockProtoPointer> pointer2;
+    testing::StrictMock<MockProtoAxisValue> axisValue1;
+    testing::StrictMock<MockProtoAxisValue> axisValue2;
+    testing::StrictMock<MockProtoAxisValue> axisValue3;
+    testing::StrictMock<MockProtoAxisValue> axisValue4;
+    testing::StrictMock<MockProtoAxisValue> axisValue5;
+    testing::StrictMock<MockProtoAxisValue> axisValue6;
+
+    EXPECT_CALL(proto, set_event_id(1));
+    EXPECT_CALL(proto, set_event_time_nanos(2));
+    EXPECT_CALL(proto, set_down_time_nanos(3));
+    EXPECT_CALL(proto, set_source(AINPUT_SOURCE_MOUSE));
+    EXPECT_CALL(proto, set_action(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+    EXPECT_CALL(proto, set_device_id(4));
+    EXPECT_CALL(proto, set_display_id(5));
+    EXPECT_CALL(proto, set_classification(AMOTION_EVENT_CLASSIFICATION_PINCH));
+    EXPECT_CALL(proto, set_flags(6));
+    EXPECT_CALL(proto, set_policy_flags(7));
+    EXPECT_CALL(proto, set_button_state(8));
+    EXPECT_CALL(proto, set_action_button(9));
+    EXPECT_CALL(proto, set_cursor_position_x(10.0f));
+    EXPECT_CALL(proto, set_cursor_position_y(11.0f));
+    EXPECT_CALL(proto, set_meta_state(12));
+    EXPECT_CALL(proto, set_precision_x(13.0f));
+    EXPECT_CALL(proto, set_precision_y(14.0f));
+
+    EXPECT_CALL(proto, add_pointer()).WillOnce(Return(&pointer1)).WillOnce(Return(&pointer2));
+
+    EXPECT_CALL(pointer1, set_pointer_id(15));
+    EXPECT_CALL(pointer1, set_tool_type(AMOTION_EVENT_TOOL_TYPE_MOUSE));
+    EXPECT_CALL(pointer1, add_axis_value())
+            .WillOnce(Return(&axisValue1))
+            .WillOnce(Return(&axisValue2))
+            .WillOnce(Return(&axisValue3));
+    EXPECT_CALL(axisValue1, set_axis(AMOTION_EVENT_AXIS_X));
+    EXPECT_CALL(axisValue1, set_value(17.0f));
+    EXPECT_CALL(axisValue2, set_axis(AMOTION_EVENT_AXIS_Y));
+    EXPECT_CALL(axisValue2, set_value(18.0f));
+    EXPECT_CALL(axisValue3, set_axis(AMOTION_EVENT_AXIS_PRESSURE));
+    EXPECT_CALL(axisValue3, set_value(19.0f));
+
+    EXPECT_CALL(pointer2, set_pointer_id(16));
+    EXPECT_CALL(pointer2, set_tool_type(AMOTION_EVENT_TOOL_TYPE_FINGER));
+    EXPECT_CALL(pointer2, add_axis_value())
+            .WillOnce(Return(&axisValue4))
+            .WillOnce(Return(&axisValue5))
+            .WillOnce(Return(&axisValue6));
+    EXPECT_CALL(axisValue4, set_axis(AMOTION_EVENT_AXIS_X));
+    EXPECT_CALL(axisValue4, set_value(20.0f));
+    EXPECT_CALL(axisValue5, set_axis(AMOTION_EVENT_AXIS_Y));
+    EXPECT_CALL(axisValue5, set_value(21.0f));
+    EXPECT_CALL(axisValue6, set_axis(AMOTION_EVENT_AXIS_PRESSURE));
+    EXPECT_CALL(axisValue6, set_value(22.0f));
+
+    TestProtoConverter::toProtoMotionEvent(event, proto, /*isRedacted=*/false);
+}
+
+TEST(AndroidInputEventProtoConverterTest, ToProtoMotionEvent_Redacted) {
+    TracedMotionEvent event{};
+    event.id = 1;
+    event.eventTime = 2;
+    event.downTime = 3;
+    event.source = AINPUT_SOURCE_MOUSE;
+    event.action = AMOTION_EVENT_ACTION_BUTTON_PRESS;
+    event.deviceId = 4;
+    event.displayId = ui::LogicalDisplayId(5);
+    event.classification = MotionClassification::PINCH;
+    event.flags = 6;
+    event.policyFlags = 7;
+    event.buttonState = 8;
+    event.actionButton = 9;
+    event.xCursorPosition = 10.0f;
+    event.yCursorPosition = 11.0f;
+    event.metaState = 12;
+    event.xPrecision = 13.0f;
+    event.yPrecision = 14.0f;
+    event.pointerProperties.emplace_back(PointerProperties{
+            .id = 15,
+            .toolType = ToolType::MOUSE,
+    });
+    event.pointerProperties.emplace_back(PointerProperties{
+            .id = 16,
+            .toolType = ToolType::FINGER,
+    });
+    event.pointerCoords.emplace_back();
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 17.0f);
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 18.0f);
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 19.0f);
+    event.pointerCoords.emplace_back();
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 20.0f);
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 21.0f);
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 22.0f);
+
+    testing::StrictMock<MockProtoMotion> proto;
+    testing::StrictMock<MockProtoPointer> pointer1;
+    testing::StrictMock<MockProtoPointer> pointer2;
+    testing::StrictMock<MockProtoAxisValue> axisValue1;
+    testing::StrictMock<MockProtoAxisValue> axisValue2;
+    testing::StrictMock<MockProtoAxisValue> axisValue3;
+    testing::StrictMock<MockProtoAxisValue> axisValue4;
+    testing::StrictMock<MockProtoAxisValue> axisValue5;
+    testing::StrictMock<MockProtoAxisValue> axisValue6;
+
+    EXPECT_CALL(proto, set_event_id(1));
+    EXPECT_CALL(proto, set_event_time_nanos(2));
+    EXPECT_CALL(proto, set_down_time_nanos(3));
+    EXPECT_CALL(proto, set_source(AINPUT_SOURCE_MOUSE));
+    EXPECT_CALL(proto, set_action(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+    EXPECT_CALL(proto, set_device_id(4));
+    EXPECT_CALL(proto, set_display_id(5));
+    EXPECT_CALL(proto, set_classification(AMOTION_EVENT_CLASSIFICATION_PINCH));
+    EXPECT_CALL(proto, set_flags(6));
+    EXPECT_CALL(proto, set_policy_flags(7));
+    EXPECT_CALL(proto, set_button_state(8));
+    EXPECT_CALL(proto, set_action_button(9));
+
+    EXPECT_CALL(proto, add_pointer()).WillOnce(Return(&pointer1)).WillOnce(Return(&pointer2));
+
+    EXPECT_CALL(pointer1, set_pointer_id(15));
+    EXPECT_CALL(pointer1, set_tool_type(AMOTION_EVENT_TOOL_TYPE_MOUSE));
+    EXPECT_CALL(pointer1, add_axis_value())
+            .WillOnce(Return(&axisValue1))
+            .WillOnce(Return(&axisValue2))
+            .WillOnce(Return(&axisValue3));
+    EXPECT_CALL(axisValue1, set_axis(AMOTION_EVENT_AXIS_X));
+    EXPECT_CALL(axisValue2, set_axis(AMOTION_EVENT_AXIS_Y));
+    EXPECT_CALL(axisValue3, set_axis(AMOTION_EVENT_AXIS_PRESSURE));
+
+    EXPECT_CALL(pointer2, set_pointer_id(16));
+    EXPECT_CALL(pointer2, set_tool_type(AMOTION_EVENT_TOOL_TYPE_FINGER));
+    EXPECT_CALL(pointer2, add_axis_value())
+            .WillOnce(Return(&axisValue4))
+            .WillOnce(Return(&axisValue5))
+            .WillOnce(Return(&axisValue6));
+    EXPECT_CALL(axisValue4, set_axis(AMOTION_EVENT_AXIS_X));
+    EXPECT_CALL(axisValue5, set_axis(AMOTION_EVENT_AXIS_Y));
+    EXPECT_CALL(axisValue6, set_axis(AMOTION_EVENT_AXIS_PRESSURE));
+
+    // Redacted fields
+    EXPECT_CALL(proto, set_meta_state(_)).Times(0);
+    EXPECT_CALL(proto, set_cursor_position_x(_)).Times(0);
+    EXPECT_CALL(proto, set_cursor_position_y(_)).Times(0);
+    EXPECT_CALL(proto, set_precision_x(_)).Times(0);
+    EXPECT_CALL(proto, set_precision_y(_)).Times(0);
+    EXPECT_CALL(axisValue1, set_value(_)).Times(0);
+    EXPECT_CALL(axisValue2, set_value(_)).Times(0);
+    EXPECT_CALL(axisValue3, set_value(_)).Times(0);
+    EXPECT_CALL(axisValue4, set_value(_)).Times(0);
+    EXPECT_CALL(axisValue5, set_value(_)).Times(0);
+    EXPECT_CALL(axisValue6, set_value(_)).Times(0);
+
+    TestProtoConverter::toProtoMotionEvent(event, proto, /*isRedacted=*/true);
+}
+
+// Test any special handling for zero values for pointer events.
+TEST(AndroidInputEventProtoConverterTest, ToProtoMotionEvent_ZeroValues) {
+    TracedMotionEvent event{};
+    event.id = 0;
+    event.eventTime = 0;
+    event.downTime = 0;
+    event.source = AINPUT_SOURCE_MOUSE;
+    event.action = AMOTION_EVENT_ACTION_BUTTON_PRESS;
+    event.deviceId = 0;
+    event.displayId = ui::LogicalDisplayId(0);
+    event.classification = {};
+    event.flags = 0;
+    event.policyFlags = 0;
+    event.buttonState = 0;
+    event.actionButton = 0;
+    event.xCursorPosition = 0.0f;
+    event.yCursorPosition = 0.0f;
+    event.metaState = 0;
+    event.xPrecision = 0.0f;
+    event.yPrecision = 0.0f;
+    event.pointerProperties.emplace_back(PointerProperties{
+            .id = 0,
+            .toolType = ToolType::MOUSE,
+    });
+    event.pointerProperties.emplace_back(PointerProperties{
+            .id = 1,
+            .toolType = ToolType::FINGER,
+    });
+    // Zero values for x and y axes are always traced for pointer events.
+    // However, zero values for other axes may not necessarily be traced.
+    event.pointerCoords.emplace_back();
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 0.0f);
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 1.0f);
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
+    event.pointerCoords.emplace_back();
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 0.0f);
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 0.0f);
+    event.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
+
+    testing::StrictMock<MockProtoMotion> proto;
+    testing::StrictMock<MockProtoPointer> pointer1;
+    testing::StrictMock<MockProtoPointer> pointer2;
+    testing::StrictMock<MockProtoAxisValue> axisValue1;
+    testing::StrictMock<MockProtoAxisValue> axisValue2;
+    testing::StrictMock<MockProtoAxisValue> axisValue3;
+    testing::StrictMock<MockProtoAxisValue> axisValue4;
+
+    EXPECT_CALL(proto, set_event_id(0));
+    EXPECT_CALL(proto, set_event_time_nanos(0));
+    EXPECT_CALL(proto, set_down_time_nanos(0));
+    EXPECT_CALL(proto, set_source(AINPUT_SOURCE_MOUSE));
+    EXPECT_CALL(proto, set_action(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+    EXPECT_CALL(proto, set_device_id(0));
+    EXPECT_CALL(proto, set_display_id(0));
+    EXPECT_CALL(proto, set_classification(0));
+    EXPECT_CALL(proto, set_flags(0));
+    EXPECT_CALL(proto, set_policy_flags(0));
+    EXPECT_CALL(proto, set_button_state(0));
+    EXPECT_CALL(proto, set_action_button(0));
+    EXPECT_CALL(proto, set_cursor_position_x(0.0f));
+    EXPECT_CALL(proto, set_cursor_position_y(0.0f));
+    EXPECT_CALL(proto, set_meta_state(0));
+    EXPECT_CALL(proto, set_precision_x(0.0f));
+    EXPECT_CALL(proto, set_precision_y(0.0f));
+
+    EXPECT_CALL(proto, add_pointer()).WillOnce(Return(&pointer1)).WillOnce(Return(&pointer2));
+
+    EXPECT_CALL(pointer1, set_pointer_id(0));
+    EXPECT_CALL(pointer1, set_tool_type(AMOTION_EVENT_TOOL_TYPE_MOUSE));
+    EXPECT_CALL(pointer1, add_axis_value())
+            .WillOnce(Return(&axisValue1))
+            .WillOnce(Return(&axisValue2));
+    EXPECT_CALL(axisValue1, set_axis(AMOTION_EVENT_AXIS_X));
+    EXPECT_CALL(axisValue1, set_value(0.0f));
+    EXPECT_CALL(axisValue2, set_axis(AMOTION_EVENT_AXIS_Y));
+    EXPECT_CALL(axisValue2, set_value(1.0f));
+
+    EXPECT_CALL(pointer2, set_pointer_id(1));
+    EXPECT_CALL(pointer2, set_tool_type(AMOTION_EVENT_TOOL_TYPE_FINGER));
+    EXPECT_CALL(pointer2, add_axis_value())
+            .WillOnce(Return(&axisValue3))
+            .WillOnce(Return(&axisValue4));
+    EXPECT_CALL(axisValue3, set_axis(AMOTION_EVENT_AXIS_X));
+    EXPECT_CALL(axisValue3, set_value(0.0f));
+    EXPECT_CALL(axisValue4, set_axis(AMOTION_EVENT_AXIS_Y));
+    EXPECT_CALL(axisValue4, set_value(0.0f));
+
+    TestProtoConverter::toProtoMotionEvent(event, proto, /*isRedacted=*/false);
+}
+
+TEST(AndroidInputEventProtoConverterTest, ToProtoKeyEvent) {
+    TracedKeyEvent event{};
+    event.id = 1;
+    event.eventTime = 2;
+    event.downTime = 3;
+    event.source = AINPUT_SOURCE_KEYBOARD;
+    event.action = AKEY_EVENT_ACTION_DOWN;
+    event.deviceId = 4;
+    event.displayId = ui::LogicalDisplayId(5);
+    event.repeatCount = 6;
+    event.flags = 7;
+    event.policyFlags = 8;
+    event.keyCode = 9;
+    event.scanCode = 10;
+    event.metaState = 11;
+
+    testing::StrictMock<MockProtoKey> proto;
+
+    EXPECT_CALL(proto, set_event_id(1));
+    EXPECT_CALL(proto, set_event_time_nanos(2));
+    EXPECT_CALL(proto, set_down_time_nanos(3));
+    EXPECT_CALL(proto, set_source(AINPUT_SOURCE_KEYBOARD));
+    EXPECT_CALL(proto, set_action(AKEY_EVENT_ACTION_DOWN));
+    EXPECT_CALL(proto, set_device_id(4));
+    EXPECT_CALL(proto, set_display_id(5));
+    EXPECT_CALL(proto, set_repeat_count(6));
+    EXPECT_CALL(proto, set_flags(7));
+    EXPECT_CALL(proto, set_policy_flags(8));
+    EXPECT_CALL(proto, set_key_code(9));
+    EXPECT_CALL(proto, set_scan_code(10));
+    EXPECT_CALL(proto, set_meta_state(11));
+
+    TestProtoConverter::toProtoKeyEvent(event, proto, /*isRedacted=*/false);
+}
+
+TEST(AndroidInputEventProtoConverterTest, ToProtoKeyEvent_Redacted) {
+    TracedKeyEvent event{};
+    event.id = 1;
+    event.eventTime = 2;
+    event.downTime = 3;
+    event.source = AINPUT_SOURCE_KEYBOARD;
+    event.action = AKEY_EVENT_ACTION_DOWN;
+    event.deviceId = 4;
+    event.displayId = ui::LogicalDisplayId(5);
+    event.repeatCount = 6;
+    event.flags = 7;
+    event.policyFlags = 8;
+    event.keyCode = 9;
+    event.scanCode = 10;
+    event.metaState = 11;
+
+    testing::StrictMock<MockProtoKey> proto;
+
+    EXPECT_CALL(proto, set_event_id(1));
+    EXPECT_CALL(proto, set_event_time_nanos(2));
+    EXPECT_CALL(proto, set_down_time_nanos(3));
+    EXPECT_CALL(proto, set_source(AINPUT_SOURCE_KEYBOARD));
+    EXPECT_CALL(proto, set_action(AKEY_EVENT_ACTION_DOWN));
+    EXPECT_CALL(proto, set_device_id(4));
+    EXPECT_CALL(proto, set_display_id(5));
+    EXPECT_CALL(proto, set_repeat_count(6));
+    EXPECT_CALL(proto, set_flags(7));
+    EXPECT_CALL(proto, set_policy_flags(8));
+
+    // Redacted fields
+    EXPECT_CALL(proto, set_key_code(_)).Times(0);
+    EXPECT_CALL(proto, set_scan_code(_)).Times(0);
+    EXPECT_CALL(proto, set_meta_state(_)).Times(0);
+
+    TestProtoConverter::toProtoKeyEvent(event, proto, /*isRedacted=*/true);
+}
+
+TEST(AndroidInputEventProtoConverterTest, ToProtoWindowDispatchEvent_Motion_IdentityTransform) {
+    TracedMotionEvent motion{};
+    motion.pointerProperties.emplace_back(PointerProperties{
+            .id = 4,
+            .toolType = ToolType::MOUSE,
+    });
+    motion.pointerCoords.emplace_back();
+    motion.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 5.0f);
+    motion.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 6.0f);
+
+    WindowDispatchArgs args{};
+    args.eventEntry = motion;
+    args.vsyncId = 1;
+    args.windowId = 2;
+    args.resolvedFlags = 3;
+    args.rawTransform = ui::Transform{};
+    args.transform = ui::Transform{};
+
+    testing::StrictMock<MockProtoDispatch> proto;
+    testing::StrictMock<MockProtoDispatchPointer> pointer;
+
+    EXPECT_CALL(proto, set_event_id(0));
+    EXPECT_CALL(proto, set_vsync_id(1));
+    EXPECT_CALL(proto, set_window_id(2));
+    EXPECT_CALL(proto, set_resolved_flags(3));
+    EXPECT_CALL(proto, add_dispatched_pointer()).WillOnce(Return(&pointer));
+    EXPECT_CALL(pointer, set_pointer_id(4));
+
+    // Since we are using identity transforms, the axis values will be identical to those in the
+    // traced event, so they should not be traced here.
+    EXPECT_CALL(pointer, add_axis_value_in_window()).Times(0);
+    EXPECT_CALL(pointer, set_x_in_display(_)).Times(0);
+    EXPECT_CALL(pointer, set_y_in_display(_)).Times(0);
+
+    TestProtoConverter::toProtoWindowDispatchEvent(args, proto, /*isRedacted=*/false);
+}
+
+TEST(AndroidInputEventProtoConverterTest, ToProtoWindowDispatchEvent_Motion_CustomTransform) {
+    TracedMotionEvent motion{};
+    motion.pointerProperties.emplace_back(PointerProperties{
+            .id = 4,
+            .toolType = ToolType::MOUSE,
+    });
+    motion.pointerCoords.emplace_back();
+    motion.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 8.0f);
+    motion.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 6.0f);
+
+    WindowDispatchArgs args{};
+    args.eventEntry = motion;
+    args.vsyncId = 1;
+    args.windowId = 2;
+    args.resolvedFlags = 3;
+    args.rawTransform.set(2, 0, 0, 0.5);
+    args.transform.set(1.0, 0, 0, 0.5);
+
+    testing::StrictMock<MockProtoDispatch> proto;
+    testing::StrictMock<MockProtoDispatchPointer> pointer;
+    testing::StrictMock<MockProtoAxisValue> axisValue1;
+
+    EXPECT_CALL(proto, set_event_id(0));
+    EXPECT_CALL(proto, set_vsync_id(1));
+    EXPECT_CALL(proto, set_window_id(2));
+    EXPECT_CALL(proto, set_resolved_flags(3));
+    EXPECT_CALL(proto, add_dispatched_pointer()).WillOnce(Return(&pointer));
+    EXPECT_CALL(pointer, set_pointer_id(4));
+
+    // Only the transformed axis-values that differ from the traced event will be traced.
+    EXPECT_CALL(pointer, add_axis_value_in_window()).WillOnce(Return(&axisValue1));
+    EXPECT_CALL(pointer, set_x_in_display(16.0f)); // MotionEvent::getRawX
+    EXPECT_CALL(pointer, set_y_in_display(3.0f));  // MotionEvent::getRawY
+
+    EXPECT_CALL(axisValue1, set_axis(AMOTION_EVENT_AXIS_Y));
+    EXPECT_CALL(axisValue1, set_value(3.0f));
+
+    TestProtoConverter::toProtoWindowDispatchEvent(args, proto, /*isRedacted=*/false);
+}
+
+TEST(AndroidInputEventProtoConverterTest, ToProtoWindowDispatchEvent_Motion_Redacted) {
+    TracedMotionEvent motion{};
+    motion.pointerProperties.emplace_back(PointerProperties{
+            .id = 4,
+            .toolType = ToolType::MOUSE,
+    });
+    motion.pointerCoords.emplace_back();
+    motion.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 5.0f);
+    motion.pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 6.0f);
+
+    WindowDispatchArgs args{};
+    args.eventEntry = motion;
+    args.vsyncId = 1;
+    args.windowId = 2;
+    args.resolvedFlags = 3;
+    args.rawTransform = ui::Transform{};
+    args.transform = ui::Transform{};
+
+    testing::StrictMock<MockProtoDispatch> proto;
+
+    EXPECT_CALL(proto, set_event_id(0));
+    EXPECT_CALL(proto, set_vsync_id(1));
+    EXPECT_CALL(proto, set_window_id(2));
+    EXPECT_CALL(proto, set_resolved_flags(3));
+
+    // Redacted fields
+    EXPECT_CALL(proto, add_dispatched_pointer()).Times(0);
+
+    TestProtoConverter::toProtoWindowDispatchEvent(args, proto, /*isRedacted=*/true);
+}
+
+TEST(AndroidInputEventProtoConverterTest, ToProtoWindowDispatchEvent_Key) {
+    TracedKeyEvent key{};
+
+    WindowDispatchArgs args{};
+    args.eventEntry = key;
+    args.vsyncId = 1;
+    args.windowId = 2;
+    args.resolvedFlags = 3;
+    args.rawTransform = ui::Transform{};
+    args.transform = ui::Transform{};
+
+    testing::StrictMock<MockProtoDispatch> proto;
+
+    EXPECT_CALL(proto, set_event_id(0));
+    EXPECT_CALL(proto, set_vsync_id(1));
+    EXPECT_CALL(proto, set_window_id(2));
+    EXPECT_CALL(proto, set_resolved_flags(3));
+
+    TestProtoConverter::toProtoWindowDispatchEvent(args, proto, /*isRedacted=*/true);
+}
+
+} // namespace
+
+} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 67b1e8c..5a14f4b 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -31,6 +31,30 @@
 
 } // namespace
 
+DisplayViewport createViewport(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
+                               ui::Rotation orientation, bool isActive, const std::string& uniqueId,
+                               std::optional<uint8_t> physicalPort, ViewportType type) {
+    const bool isRotated = orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270;
+    DisplayViewport v;
+    v.displayId = displayId;
+    v.orientation = orientation;
+    v.logicalLeft = 0;
+    v.logicalTop = 0;
+    v.logicalRight = isRotated ? height : width;
+    v.logicalBottom = isRotated ? width : height;
+    v.physicalLeft = 0;
+    v.physicalTop = 0;
+    v.physicalRight = isRotated ? height : width;
+    v.physicalBottom = isRotated ? width : height;
+    v.deviceWidth = isRotated ? height : width;
+    v.deviceHeight = isRotated ? width : height;
+    v.isActive = isActive;
+    v.uniqueId = uniqueId;
+    v.physicalPort = physicalPort;
+    v.type = type;
+    return v;
+};
+
 void FakeInputReaderPolicy::assertInputDevicesChanged() {
     waitForInputDevices(
             [](bool devicesChanged) {
@@ -115,33 +139,6 @@
     mConfig.setDisplayViewports(mViewports);
 }
 
-void FakeInputReaderPolicy::addDisplayViewport(ui::LogicalDisplayId displayId, int32_t width,
-                                               int32_t height, ui::Rotation orientation,
-                                               bool isActive, const std::string& uniqueId,
-                                               std::optional<uint8_t> physicalPort,
-                                               ViewportType type) {
-    const bool isRotated = orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270;
-    DisplayViewport v;
-    v.displayId = displayId;
-    v.orientation = orientation;
-    v.logicalLeft = 0;
-    v.logicalTop = 0;
-    v.logicalRight = isRotated ? height : width;
-    v.logicalBottom = isRotated ? width : height;
-    v.physicalLeft = 0;
-    v.physicalTop = 0;
-    v.physicalRight = isRotated ? height : width;
-    v.physicalBottom = isRotated ? width : height;
-    v.deviceWidth = isRotated ? height : width;
-    v.deviceHeight = isRotated ? width : height;
-    v.isActive = isActive;
-    v.uniqueId = uniqueId;
-    v.physicalPort = physicalPort;
-    v.type = type;
-
-    addDisplayViewport(v);
-}
-
 bool FakeInputReaderPolicy::updateViewport(const DisplayViewport& viewport) {
     size_t count = mViewports.size();
     for (size_t i = 0; i < count; i++) {
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 42c9567..9dce31a 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -31,6 +31,10 @@
 
 namespace android {
 
+DisplayViewport createViewport(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
+                               ui::Rotation orientation, bool isActive, const std::string& uniqueId,
+                               std::optional<uint8_t> physicalPort, ViewportType type);
+
 class FakeInputReaderPolicy : public InputReaderPolicyInterface {
 protected:
     virtual ~FakeInputReaderPolicy() {}
@@ -50,9 +54,6 @@
     std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const;
     std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t displayPort) const;
     void addDisplayViewport(DisplayViewport viewport);
-    void addDisplayViewport(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
-                            ui::Rotation orientation, bool isActive, const std::string& uniqueId,
-                            std::optional<uint8_t> physicalPort, ViewportType type);
     bool updateViewport(const DisplayViewport& viewport);
     void addExcludedDeviceName(const std::string& deviceName);
     void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort);
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 6b4c4b7..368db1b 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -8101,6 +8101,17 @@
     ASSERT_EQ(keyArgs.scanCode, verifiedKey.scanCode);
     ASSERT_EQ(keyArgs.metaState, verifiedKey.metaState);
     ASSERT_EQ(0, verifiedKey.repeatCount);
+
+    // InputEvent and subclasses don't have a virtual destructor and only
+    // InputEvent's destructor gets called when `verified` goes out of scope,
+    // even if `verifyInputEvent` returns an object of a subclass.  To fix this,
+    // we should either consider using std::variant in some way, or introduce an
+    // intermediate POD data structure that we will put the data into just prior
+    // to signing.  Adding virtual functions to these classes is undesirable as
+    // the bytes in these objects are getting signed.  As a temporary fix, cast
+    // the pointer to the correct class (which is statically known) before
+    // destruction.
+    delete (VerifiedKeyEvent*)verified.release();
 }
 
 TEST_F(InputDispatcherTest, VerifyInputEvent_MotionEvent) {
@@ -8148,6 +8159,10 @@
     EXPECT_EQ(motionArgs.downTime, verifiedMotion.downTimeNanos);
     EXPECT_EQ(motionArgs.metaState, verifiedMotion.metaState);
     EXPECT_EQ(motionArgs.buttonState, verifiedMotion.buttonState);
+
+    // Cast to the correct type before destruction.  See explanation at the end
+    // of the VerifyInputEvent_KeyEvent test.
+    delete (VerifiedMotionEvent*)verified.release();
 }
 
 /**
@@ -9890,6 +9905,15 @@
                        AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT);
 }
 
+TEST_F(InputFilterInjectionPolicyTest,
+       MotionEventsInjectedFromAccessibilityTool_HaveAccessibilityFlags) {
+    testInjectedMotion(POLICY_FLAG_FILTERED | POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY |
+                               POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL,
+                       /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3,
+                       AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT |
+                               AMOTION_EVENT_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL);
+}
+
 TEST_F(InputFilterInjectionPolicyTest, RegularInjectedEvents_ReceiveVirtualDeviceId) {
     testInjectedKey(/*policyFlags=*/0, /*injectedDeviceId=*/3,
                     /*resolvedDeviceId=*/VIRTUAL_KEYBOARD_ID, /*flags=*/0);
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index 77f42f2..652a658 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -186,8 +186,10 @@
                                                    const std::string& uniqueId,
                                                    std::optional<uint8_t> physicalPort,
                                                    ViewportType viewportType) {
-    mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /* isActive= */ true,
-                                    uniqueId, physicalPort, viewportType);
+    DisplayViewport viewport =
+            createViewport(displayId, width, height, orientation, /* isActive= */ true, uniqueId,
+                           physicalPort, viewportType);
+    mFakePolicy->addDisplayViewport(viewport);
     configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
 }
 
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 50cbedf..a8e4736 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -385,30 +385,29 @@
     static const std::string uniqueId = "local:0";
 
     // We didn't add any viewports yet, so there shouldn't be any.
-    std::optional<DisplayViewport> internalViewport =
-            mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
-    ASSERT_FALSE(internalViewport);
+    ASSERT_FALSE(mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL));
 
     // Add an internal viewport, then clear it
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/true, uniqueId, NO_PORT, ViewportType::INTERNAL);
-
+    DisplayViewport internalViewport =
+            createViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, uniqueId, NO_PORT, ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(internalViewport);
     // Check matching by uniqueId
-    internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId);
-    ASSERT_TRUE(internalViewport);
-    ASSERT_EQ(ViewportType::INTERNAL, internalViewport->type);
+    std::optional<DisplayViewport> receivedInternalViewport =
+            mFakePolicy->getDisplayViewportByUniqueId(uniqueId);
+    ASSERT_TRUE(receivedInternalViewport.has_value());
+    ASSERT_EQ(internalViewport, *receivedInternalViewport);
 
     // Check matching by viewport type
-    internalViewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
-    ASSERT_TRUE(internalViewport);
-    ASSERT_EQ(uniqueId, internalViewport->uniqueId);
+    receivedInternalViewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    ASSERT_TRUE(receivedInternalViewport.has_value());
+    ASSERT_EQ(internalViewport, *receivedInternalViewport);
 
     mFakePolicy->clearViewports();
+
     // Make sure nothing is found after clear
-    internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId);
-    ASSERT_FALSE(internalViewport);
-    internalViewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
-    ASSERT_FALSE(internalViewport);
+    ASSERT_FALSE(mFakePolicy->getDisplayViewportByUniqueId(uniqueId));
+    ASSERT_FALSE(mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL));
 }
 
 TEST_F(InputReaderPolicyTest, Viewports_GetByType) {
@@ -420,49 +419,49 @@
     constexpr ui::LogicalDisplayId virtualDisplayId2 = ui::LogicalDisplayId{3};
 
     // Add an internal viewport
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/true, internalUniqueId, NO_PORT,
-                                    ViewportType::INTERNAL);
+    DisplayViewport internalViewport =
+            createViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, internalUniqueId, NO_PORT, ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(internalViewport);
     // Add an external viewport
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/true, externalUniqueId, NO_PORT,
-                                    ViewportType::EXTERNAL);
+    DisplayViewport externalViewport =
+            createViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, externalUniqueId, NO_PORT, ViewportType::EXTERNAL);
+    mFakePolicy->addDisplayViewport(externalViewport);
     // Add an virtual viewport
-    mFakePolicy->addDisplayViewport(virtualDisplayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, /*isActive=*/true, virtualUniqueId1, NO_PORT,
-                                    ViewportType::VIRTUAL);
+    DisplayViewport virtualViewport1 =
+            createViewport(virtualDisplayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, virtualUniqueId1, NO_PORT, ViewportType::VIRTUAL);
+    mFakePolicy->addDisplayViewport(virtualViewport1);
     // Add another virtual viewport
-    mFakePolicy->addDisplayViewport(virtualDisplayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, /*isActive=*/true, virtualUniqueId2, NO_PORT,
-                                    ViewportType::VIRTUAL);
+    DisplayViewport virtualViewport2 =
+            createViewport(virtualDisplayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, virtualUniqueId2, NO_PORT, ViewportType::VIRTUAL);
+    mFakePolicy->addDisplayViewport(virtualViewport2);
 
     // Check matching by type for internal
-    std::optional<DisplayViewport> internalViewport =
+    std::optional<DisplayViewport> receivedInternalViewport =
             mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
-    ASSERT_TRUE(internalViewport);
-    ASSERT_EQ(internalUniqueId, internalViewport->uniqueId);
+    ASSERT_TRUE(receivedInternalViewport.has_value());
+    ASSERT_EQ(internalViewport, *receivedInternalViewport);
 
     // Check matching by type for external
-    std::optional<DisplayViewport> externalViewport =
+    std::optional<DisplayViewport> receivedExternalViewport =
             mFakePolicy->getDisplayViewportByType(ViewportType::EXTERNAL);
-    ASSERT_TRUE(externalViewport);
-    ASSERT_EQ(externalUniqueId, externalViewport->uniqueId);
+    ASSERT_TRUE(receivedExternalViewport.has_value());
+    ASSERT_EQ(externalViewport, *receivedExternalViewport);
 
     // Check matching by uniqueId for virtual viewport #1
-    std::optional<DisplayViewport> virtualViewport1 =
+    std::optional<DisplayViewport> receivedVirtualViewport1 =
             mFakePolicy->getDisplayViewportByUniqueId(virtualUniqueId1);
-    ASSERT_TRUE(virtualViewport1);
-    ASSERT_EQ(ViewportType::VIRTUAL, virtualViewport1->type);
-    ASSERT_EQ(virtualUniqueId1, virtualViewport1->uniqueId);
-    ASSERT_EQ(virtualDisplayId1, virtualViewport1->displayId);
+    ASSERT_TRUE(receivedVirtualViewport1.has_value());
+    ASSERT_EQ(virtualViewport1, *receivedVirtualViewport1);
 
     // Check matching by uniqueId for virtual viewport #2
-    std::optional<DisplayViewport> virtualViewport2 =
+    std::optional<DisplayViewport> receivedVirtualViewport2 =
             mFakePolicy->getDisplayViewportByUniqueId(virtualUniqueId2);
-    ASSERT_TRUE(virtualViewport2);
-    ASSERT_EQ(ViewportType::VIRTUAL, virtualViewport2->type);
-    ASSERT_EQ(virtualUniqueId2, virtualViewport2->uniqueId);
-    ASSERT_EQ(virtualDisplayId2, virtualViewport2->displayId);
+    ASSERT_TRUE(receivedVirtualViewport2.has_value());
+    ASSERT_EQ(virtualViewport2, *receivedVirtualViewport2);
 }
 
 
@@ -482,24 +481,26 @@
     for (const ViewportType& type : types) {
         mFakePolicy->clearViewports();
         // Add a viewport
-        mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                        /*isActive=*/true, uniqueId1, NO_PORT, type);
+        DisplayViewport viewport1 =
+                createViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                               /*isActive=*/true, uniqueId1, NO_PORT, type);
+        mFakePolicy->addDisplayViewport(viewport1);
         // Add another viewport
-        mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                        /*isActive=*/true, uniqueId2, NO_PORT, type);
+        DisplayViewport viewport2 =
+                createViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                               /*isActive=*/true, uniqueId2, NO_PORT, type);
+        mFakePolicy->addDisplayViewport(viewport2);
 
         // Check that correct display viewport was returned by comparing the display IDs.
-        std::optional<DisplayViewport> viewport1 =
+        std::optional<DisplayViewport> receivedViewport1 =
                 mFakePolicy->getDisplayViewportByUniqueId(uniqueId1);
-        ASSERT_TRUE(viewport1);
-        ASSERT_EQ(displayId1, viewport1->displayId);
-        ASSERT_EQ(type, viewport1->type);
+        ASSERT_TRUE(receivedViewport1.has_value());
+        ASSERT_EQ(viewport1, *receivedViewport1);
 
-        std::optional<DisplayViewport> viewport2 =
+        std::optional<DisplayViewport> receivedViewport2 =
                 mFakePolicy->getDisplayViewportByUniqueId(uniqueId2);
-        ASSERT_TRUE(viewport2);
-        ASSERT_EQ(displayId2, viewport2->displayId);
-        ASSERT_EQ(type, viewport2->type);
+        ASSERT_TRUE(receivedViewport2.has_value());
+        ASSERT_EQ(viewport2, *receivedViewport2);
 
         // When there are multiple viewports of the same kind, and uniqueId is not specified
         // in the call to getDisplayViewport, then that situation is not supported.
@@ -525,32 +526,27 @@
 
     // Add the default display first and ensure it gets returned.
     mFakePolicy->clearViewports();
-    mFakePolicy->addDisplayViewport(ui::LogicalDisplayId::DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT,
-                                    ViewportType::INTERNAL);
-    mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT,
-                                    ViewportType::INTERNAL);
-
-    std::optional<DisplayViewport> viewport =
+    DisplayViewport viewport1 = createViewport(ui::LogicalDisplayId::DEFAULT, DISPLAY_WIDTH,
+                                               DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/true,
+                                               uniqueId1, NO_PORT, ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(viewport1);
+    DisplayViewport viewport2 =
+            createViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, uniqueId2, NO_PORT, ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(viewport2);
+    std::optional<DisplayViewport> receivedViewport =
             mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
-    ASSERT_TRUE(viewport);
-    ASSERT_EQ(ui::LogicalDisplayId::DEFAULT, viewport->displayId);
-    ASSERT_EQ(ViewportType::INTERNAL, viewport->type);
+    ASSERT_TRUE(receivedViewport.has_value());
+    ASSERT_EQ(viewport1, *receivedViewport);
 
     // Add the default display second to make sure order doesn't matter.
     mFakePolicy->clearViewports();
-    mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT,
-                                    ViewportType::INTERNAL);
-    mFakePolicy->addDisplayViewport(ui::LogicalDisplayId::DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT,
-                                    ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(viewport2);
+    mFakePolicy->addDisplayViewport(viewport1);
 
-    viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
-    ASSERT_TRUE(viewport);
-    ASSERT_EQ(ui::LogicalDisplayId::DEFAULT, viewport->displayId);
-    ASSERT_EQ(ViewportType::INTERNAL, viewport->type);
+    receivedViewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    ASSERT_TRUE(receivedViewport.has_value());
+    ASSERT_EQ(viewport1, *receivedViewport);
 }
 
 /**
@@ -568,28 +564,27 @@
 
     mFakePolicy->clearViewports();
     // Add a viewport that's associated with some display port that's not of interest.
-    mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/true, uniqueId1, hdmi3, type);
+    DisplayViewport viewport1 =
+            createViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, uniqueId1, hdmi3, type);
+    mFakePolicy->addDisplayViewport(viewport1);
     // Add another viewport, connected to HDMI1 port
-    mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/true, uniqueId2, hdmi1, type);
-
+    DisplayViewport viewport2 =
+            createViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, uniqueId2, hdmi1, type);
+    mFakePolicy->addDisplayViewport(viewport2);
     // Check that correct display viewport was returned by comparing the display ports.
     std::optional<DisplayViewport> hdmi1Viewport = mFakePolicy->getDisplayViewportByPort(hdmi1);
-    ASSERT_TRUE(hdmi1Viewport);
-    ASSERT_EQ(displayId2, hdmi1Viewport->displayId);
-    ASSERT_EQ(uniqueId2, hdmi1Viewport->uniqueId);
+    ASSERT_TRUE(hdmi1Viewport.has_value());
+    ASSERT_EQ(viewport2, *hdmi1Viewport);
 
     // Check that we can still get the same viewport using the uniqueId
     hdmi1Viewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId2);
-    ASSERT_TRUE(hdmi1Viewport);
-    ASSERT_EQ(displayId2, hdmi1Viewport->displayId);
-    ASSERT_EQ(uniqueId2, hdmi1Viewport->uniqueId);
-    ASSERT_EQ(type, hdmi1Viewport->type);
+    ASSERT_TRUE(hdmi1Viewport.has_value());
+    ASSERT_EQ(viewport2, *hdmi1Viewport);
 
     // Check that we cannot find a port with "HDMI2", because we never added one
-    std::optional<DisplayViewport> hdmi2Viewport = mFakePolicy->getDisplayViewportByPort(hdmi2);
-    ASSERT_FALSE(hdmi2Viewport);
+    ASSERT_FALSE(mFakePolicy->getDisplayViewportByPort(hdmi2));
 }
 
 // --- InputReaderTest ---
@@ -1046,11 +1041,14 @@
 
     // Add default and second display.
     mFakePolicy->clearViewports();
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
-    mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, /*isActive=*/true, "local:1", hdmi1,
-                                    ViewportType::EXTERNAL);
+    DisplayViewport internalViewport =
+            createViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(internalViewport);
+    DisplayViewport externalViewport =
+            createViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, "local:1", hdmi1, ViewportType::EXTERNAL);
+    mFakePolicy->addDisplayViewport(externalViewport);
     mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::DISPLAY_INFO);
     mReader->loopOnce();
 
@@ -1653,7 +1651,7 @@
         mDevice = createUinputDevice<UinputTouchScreen>(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
         const auto info = waitForDevice(mDevice->getName());
-        ASSERT_TRUE(info);
+        ASSERT_TRUE(info.has_value());
         mDeviceInfo = *info;
     }
 
@@ -1661,8 +1659,10 @@
                                       ui::Rotation orientation, const std::string& uniqueId,
                                       std::optional<uint8_t> physicalPort,
                                       ViewportType viewportType) {
-        mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /*isActive=*/true,
-                                        uniqueId, physicalPort, viewportType);
+        DisplayViewport viewport =
+                createViewport(displayId, width, height, orientation, /*isActive=*/true, uniqueId,
+                               physicalPort, viewportType);
+        mFakePolicy->addDisplayViewport(viewport);
         mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::DISPLAY_INFO);
     }
 
@@ -1721,7 +1721,7 @@
                                      ViewportType::INTERNAL);
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
         const auto info = waitForDevice(mDevice->getName());
-        ASSERT_TRUE(info);
+        ASSERT_TRUE(info.has_value());
         mDeviceInfo = *info;
     }
 };
@@ -2053,7 +2053,7 @@
     auto externalStylus = createUinputDevice<UinputExternalStylus>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
     const auto stylusInfo = waitForDevice(externalStylus->getName());
-    ASSERT_TRUE(stylusInfo);
+    ASSERT_TRUE(stylusInfo.has_value());
 
     // Move
     mDevice->sendMove(centerPoint + Point(2, 2));
@@ -2122,7 +2122,7 @@
         mStylus = mStylusDeviceLifecycleTracker.get();
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
         const auto info = waitForDevice(mStylus->getName());
-        ASSERT_TRUE(info);
+        ASSERT_TRUE(info.has_value());
         mStylusInfo = *info;
     }
 
@@ -2395,7 +2395,7 @@
 
     // Connecting an external stylus changes the source of the touchscreen.
     const auto deviceInfo = waitForDevice(mDevice->getName());
-    ASSERT_TRUE(deviceInfo);
+    ASSERT_TRUE(deviceInfo.has_value());
     ASSERT_TRUE(isFromSource(deviceInfo->getSources(), STYLUS_FUSION_SOURCE));
 }
 
@@ -2408,7 +2408,7 @@
             createUinputDevice<UinputExternalStylusWithPressure>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
     const auto stylusInfo = waitForDevice(stylus->getName());
-    ASSERT_TRUE(stylusInfo);
+    ASSERT_TRUE(stylusInfo.has_value());
 
     ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
 
@@ -2453,7 +2453,7 @@
             createUinputDevice<UinputExternalStylusWithPressure>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
     const auto stylusInfo = waitForDevice(stylus->getName());
-    ASSERT_TRUE(stylusInfo);
+    ASSERT_TRUE(stylusInfo.has_value());
 
     ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
 
@@ -2880,9 +2880,10 @@
     ASSERT_FALSE(mDevice->isEnabled());
 
     // Prepare displays.
-    mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, /*isActive=*/true, UNIQUE_ID, hdmi,
-                                    ViewportType::INTERNAL);
+    DisplayViewport viewport =
+            createViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, UNIQUE_ID, hdmi, ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(viewport);
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                  InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_TRUE(mDevice->isEnabled());
@@ -2917,9 +2918,12 @@
     ASSERT_FALSE(mDevice->isEnabled());
 
     // Device should be enabled when a display is found.
-    mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
-                                    NO_PORT, ViewportType::INTERNAL);
+
+    DisplayViewport secondViewport =
+            createViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /* isActive= */ true, DISPLAY_UNIQUE_ID, NO_PORT,
+                           ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(secondViewport);
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                  InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_TRUE(mDevice->isEnabled());
@@ -2945,9 +2949,12 @@
                                /*changes=*/{});
 
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
-    mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
-                                    NO_PORT, ViewportType::INTERNAL);
+
+    DisplayViewport secondViewport =
+            createViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /* isActive= */ true, DISPLAY_UNIQUE_ID, NO_PORT,
+                           ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(secondViewport);
     const auto initialGeneration = mDevice->getGeneration();
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                  InputReaderConfiguration::Change::DISPLAY_INFO);
@@ -7594,8 +7601,10 @@
 TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     // Don't set touch.enableForInactiveViewport to verify the default behavior.
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+    DisplayViewport viewport =
+            createViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(viewport);
     configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
     prepareAxes(POSITION);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
@@ -7614,8 +7623,10 @@
 TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreProcessed) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     addConfigurationProperty("touch.enableForInactiveViewport", "1");
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+    DisplayViewport viewport =
+            createViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(viewport);
     configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
     prepareAxes(POSITION);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
@@ -7636,8 +7647,10 @@
 TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     addConfigurationProperty("touch.enableForInactiveViewport", "0");
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/true, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+    DisplayViewport viewport =
+            createViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(viewport);
     std::optional<DisplayViewport> optionalDisplayViewport =
             mFakePolicy->getDisplayViewportByUniqueId(UNIQUE_ID);
     ASSERT_TRUE(optionalDisplayViewport.has_value());
@@ -7692,12 +7705,10 @@
 TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_TouchesNotAborted) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     addConfigurationProperty("touch.enableForInactiveViewport", "1");
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/true, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
-    std::optional<DisplayViewport> optionalDisplayViewport =
-            mFakePolicy->getDisplayViewportByUniqueId(UNIQUE_ID);
-    ASSERT_TRUE(optionalDisplayViewport.has_value());
-    DisplayViewport displayViewport = *optionalDisplayViewport;
+    DisplayViewport displayViewport =
+            createViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(displayViewport);
 
     configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
     prepareAxes(POSITION);
diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
index 0ea22d4..b7cb348 100644
--- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
+++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
@@ -109,9 +109,10 @@
         mockSlotValues({});
 
         mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-        mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                        /*isActive=*/true, "local:0", NO_PORT,
-                                        ViewportType::INTERNAL);
+        DisplayViewport internalViewport =
+                createViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                               /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
+        mFakePolicy->addDisplayViewport(internalViewport);
         mMapper = createInputMapper<MultiTouchInputMapper>(*mDeviceContext,
                                                            mFakePolicy->getReaderConfiguration());
     }
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
index 0789114..5f5aa63 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -124,9 +124,10 @@
  */
 TEST_F(TouchpadInputMapperTest, HoverAndLeftButtonPress) {
     mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
-                                    /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
-
+    DisplayViewport viewport =
+            createViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                           /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(viewport);
     std::list<NotifyArgs> args;
 
     args += mMapper->reconfigure(systemTime(SYSTEM_TIME_MONOTONIC), mReaderConfiguration,
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index e1c5383..d117753 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1070,7 +1070,8 @@
 void SurfaceFlinger::initBootProperties() {
     property_set("service.sf.present_timestamp", mHasReliablePresentFences ? "1" : "0");
 
-    if (base::GetBoolProperty("debug.sf.boot_animation"s, true)) {
+    if (base::GetBoolProperty("debug.sf.boot_animation"s, true) &&
+        (base::GetIntProperty("debug.sf.nobootanimation"s, 0) == 0)) {
         // Reset and (if needed) start BootAnimation.
         property_set("service.bootanim.exit", "0");
         property_set("service.bootanim.progress", "0");