Merge "Remove API level mentions from typedefs" into main
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 4b7af45..3711362 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -683,7 +683,7 @@
     return err;
 }
 
-int Parcel::compareData(const Parcel& other) {
+int Parcel::compareData(const Parcel& other) const {
     size_t size = dataSize();
     if (size != other.dataSize()) {
         return size < other.dataSize() ? -1 : 1;
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 5cc0830..e2b23be 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -92,7 +92,7 @@
 
     LIBBINDER_EXPORTED status_t appendFrom(const Parcel* parcel, size_t start, size_t len);
 
-    LIBBINDER_EXPORTED int compareData(const Parcel& other);
+    LIBBINDER_EXPORTED int compareData(const Parcel& other) const;
     LIBBINDER_EXPORTED status_t compareDataInRange(size_t thisOffset, const Parcel& other,
                                                    size_t otherOffset, size_t length,
                                                    int* result) const;
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index 2deb254..4545d7b 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -15,6 +15,8 @@
         "libbinder_ndk_sys",
         "libdowncast_rs",
         "liblibc",
+        "liblog_rust",
+        "libnix",
     ],
     host_supported: true,
     vendor_available: true,
@@ -79,6 +81,9 @@
     shared_libs: [
         "libbinder_ndk",
     ],
+    rustlibs: [
+        "liblibc",
+    ],
     host_supported: true,
     vendor_available: true,
     product_available: true,
@@ -129,9 +134,18 @@
         // rustified
         "libbinder_ndk_bindgen_flags.txt",
     ],
+    bindgen_flags: [
+        "--blocklist-type",
+        "sockaddr",
+        "--raw-line",
+        "use libc::sockaddr;",
+    ],
     shared_libs: [
         "libbinder_ndk",
     ],
+    rustlibs: [
+        "liblibc",
+    ],
     host_supported: true,
     vendor_available: true,
     product_available: true,
@@ -185,6 +199,8 @@
         "libbinder_ndk_sys",
         "libdowncast_rs",
         "liblibc",
+        "liblog_rust",
+        "libnix",
     ],
 }
 
@@ -196,4 +212,7 @@
     auto_gen_config: true,
     clippy_lints: "none",
     lints: "none",
+    rustlibs: [
+        "liblibc",
+    ],
 }
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index e048696..0e8e388 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -104,6 +104,8 @@
 mod service;
 #[cfg(not(trusty))]
 mod state;
+#[cfg(not(any(android_vendor, android_vndk)))]
+mod system_only;
 
 use binder_ndk_sys as sys;
 
@@ -120,6 +122,8 @@
 };
 #[cfg(not(trusty))]
 pub use state::{ProcessState, ThreadState};
+#[cfg(not(any(android_vendor, android_vndk)))]
+pub use system_only::{Accessor, ConnectionInfo};
 
 /// Binder result containing a [`Status`] on error.
 pub type Result<T> = std::result::Result<T, Status>;
diff --git a/libs/binder/rust/src/system_only.rs b/libs/binder/rust/src/system_only.rs
new file mode 100644
index 0000000..a91d84d
--- /dev/null
+++ b/libs/binder/rust/src/system_only.rs
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::proxy::SpIBinder;
+use crate::sys;
+
+use std::ffi::{c_void, CStr, CString};
+use std::os::raw::c_char;
+
+use libc::sockaddr;
+use nix::sys::socket::{SockaddrLike, UnixAddr, VsockAddr};
+use std::sync::Arc;
+use std::{fmt, ptr};
+
+/// Rust wrapper around ABinderRpc_Accessor objects for RPC binder service management.
+///
+/// Dropping the `Accessor` will drop the underlying object and the binder it owns.
+pub struct Accessor {
+    accessor: *mut sys::ABinderRpc_Accessor,
+}
+
+impl fmt::Debug for Accessor {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "ABinderRpc_Accessor({:p})", self.accessor)
+    }
+}
+
+/// Socket connection info required for libbinder to connect to a service.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ConnectionInfo {
+    /// For vsock connection
+    Vsock(VsockAddr),
+    /// For unix domain socket connection
+    Unix(UnixAddr),
+}
+
+/// Safety: A `Accessor` is a wrapper around `ABinderRpc_Accessor` which is
+/// `Sync` and `Send`. As
+/// `ABinderRpc_Accessor` is threadsafe, this structure is too.
+/// The Fn owned the Accessor has `Sync` and `Send` properties
+unsafe impl Send for Accessor {}
+
+/// Safety: A `Accessor` is a wrapper around `ABinderRpc_Accessor` which is
+/// `Sync` and `Send`. As `ABinderRpc_Accessor` is threadsafe, this structure is too.
+/// The Fn owned the Accessor has `Sync` and `Send` properties
+unsafe impl Sync for Accessor {}
+
+impl Accessor {
+    /// Create a new accessor that will call the given callback when its
+    /// connection info is required.
+    /// The callback object and all objects it captures are owned by the Accessor
+    /// and will be deleted some time after the Accessor is Dropped. If the callback
+    /// is being called when the Accessor is Dropped, the callback will not be deleted
+    /// immediately.
+    pub fn new<F>(instance: &str, callback: F) -> Accessor
+    where
+        F: Fn(&str) -> Option<ConnectionInfo> + Send + Sync + 'static,
+    {
+        let callback: *mut c_void = Arc::into_raw(Arc::new(callback)) as *mut c_void;
+        let inst = CString::new(instance).unwrap();
+
+        // Safety: The function pointer is a valid connection_info callback.
+        // This call returns an owned `ABinderRpc_Accessor` pointer which
+        // must be destroyed via `ABinderRpc_Accessor_delete` when no longer
+        // needed.
+        // When the underlying ABinderRpc_Accessor is deleted, it will call
+        // the cookie_decr_refcount callback to release its strong ref.
+        let accessor = unsafe {
+            sys::ABinderRpc_Accessor_new(
+                inst.as_ptr(),
+                Some(Self::connection_info::<F>),
+                callback,
+                Some(Self::cookie_decr_refcount::<F>),
+            )
+        };
+
+        Accessor { accessor }
+    }
+
+    /// Get the underlying binder for this Accessor for when it needs to be either
+    /// registered with service manager or sent to another process.
+    pub fn as_binder(&self) -> Option<SpIBinder> {
+        // Safety: `ABinderRpc_Accessor_asBinder` returns either a null pointer or a
+        // valid pointer to an owned `AIBinder`. Either of these values is safe to
+        // pass to `SpIBinder::from_raw`.
+        unsafe { SpIBinder::from_raw(sys::ABinderRpc_Accessor_asBinder(self.accessor)) }
+    }
+
+    /// Callback invoked from C++ when the connection info is needed.
+    ///
+    /// # Safety
+    ///
+    /// The `instance` parameter must be a non-null pointer to a valid C string for
+    /// CStr::from_ptr. The memory must contain a valid null terminator at the end of
+    /// the string within isize::MAX from the pointer. The memory must not be mutated for
+    /// the duration of this function  call and must be valid for reads from the pointer
+    /// to the null terminator.
+    /// The `cookie` parameter must be the cookie for an `Arc<F>` and
+    /// the caller must hold a ref-count to it.
+    unsafe extern "C" fn connection_info<F>(
+        instance: *const c_char,
+        cookie: *mut c_void,
+    ) -> *mut binder_ndk_sys::ABinderRpc_ConnectionInfo
+    where
+        F: Fn(&str) -> Option<ConnectionInfo> + Send + Sync + 'static,
+    {
+        if cookie.is_null() || instance.is_null() {
+            log::error!("Cookie({cookie:p}) or instance({instance:p}) is null!");
+            return ptr::null_mut();
+        }
+        // Safety: The caller promises that `cookie` is for an Arc<F>.
+        let callback = unsafe { (cookie as *const F).as_ref().unwrap() };
+
+        // Safety: The caller in libbinder_ndk will have already verified this is a valid
+        // C string
+        let inst = unsafe {
+            match CStr::from_ptr(instance).to_str() {
+                Ok(s) => s,
+                Err(err) => {
+                    log::error!("Failed to get a valid C string! {err:?}");
+                    return ptr::null_mut();
+                }
+            }
+        };
+
+        let connection = match callback(inst) {
+            Some(con) => con,
+            None => {
+                return ptr::null_mut();
+            }
+        };
+
+        match connection {
+            ConnectionInfo::Vsock(addr) => {
+                // Safety: The sockaddr is being copied in the NDK API
+                unsafe { sys::ABinderRpc_ConnectionInfo_new(addr.as_ptr(), addr.len()) }
+            }
+            ConnectionInfo::Unix(addr) => {
+                // Safety: The sockaddr is being copied in the NDK API
+                // The cast is from sockaddr_un* to sockaddr*.
+                unsafe {
+                    sys::ABinderRpc_ConnectionInfo_new(addr.as_ptr() as *const sockaddr, addr.len())
+                }
+            }
+        }
+    }
+
+    /// Callback that decrements the ref-count.
+    /// This is invoked from C++ when a binder is unlinked.
+    ///
+    /// # Safety
+    ///
+    /// The `cookie` parameter must be the cookie for an `Arc<F>` and
+    /// the owner must give up a ref-count to it.
+    unsafe extern "C" fn cookie_decr_refcount<F>(cookie: *mut c_void)
+    where
+        F: Fn(&str) -> Option<ConnectionInfo> + Send + Sync + 'static,
+    {
+        // Safety: The caller promises that `cookie` is for an Arc<F>.
+        unsafe { Arc::decrement_strong_count(cookie as *const F) };
+    }
+}
+
+impl Drop for Accessor {
+    fn drop(&mut self) {
+        // Safety: `self.accessor` is always a valid, owned
+        // `ABinderRpc_Accessor` pointer returned by
+        // `ABinderRpc_Accessor_new` when `self` was created. This delete
+        // method can only be called once when `self` is dropped.
+        unsafe {
+            sys::ABinderRpc_Accessor_delete(self.accessor);
+        }
+    }
+}
diff --git a/libs/binder/rust/sys/BinderBindings.hpp b/libs/binder/rust/sys/BinderBindings.hpp
index 65fa2ca..bd666fe 100644
--- a/libs/binder/rust/sys/BinderBindings.hpp
+++ b/libs/binder/rust/sys/BinderBindings.hpp
@@ -20,6 +20,7 @@
 #include <android/binder_parcel.h>
 #include <android/binder_parcel_platform.h>
 #include <android/binder_process.h>
+#include <android/binder_rpc.h>
 #include <android/binder_shell.h>
 #include <android/binder_stability.h>
 #include <android/binder_status.h>
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 5359832..bdb7e4a 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -384,8 +384,8 @@
     use std::time::Duration;
 
     use binder::{
-        BinderFeatures, DeathRecipient, FromIBinder, IBinder, Interface, SpIBinder, StatusCode,
-        Strong,
+        Accessor, BinderFeatures, DeathRecipient, FromIBinder, IBinder, Interface, SpIBinder,
+        StatusCode, Strong,
     };
     // Import from impl API for testing only, should not be necessary as long as
     // you are using AIDL.
@@ -908,6 +908,43 @@
         assert_eq!(service.test().unwrap(), service_name);
     }
 
+    struct ToBeDeleted {
+        deleted: Arc<AtomicBool>,
+    }
+
+    impl Drop for ToBeDeleted {
+        fn drop(&mut self) {
+            assert!(!self.deleted.load(Ordering::Relaxed));
+            self.deleted.store(true, Ordering::Relaxed);
+        }
+    }
+
+    #[test]
+    fn test_accessor_callback_destruction() {
+        let deleted: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
+        {
+            let accessor: Accessor;
+            {
+                let helper = ToBeDeleted { deleted: deleted.clone() };
+                let get_connection_info = move |_instance: &str| {
+                    // Capture this object so we can see it get destructed
+                    // after the parent scope
+                    let _ = &helper;
+                    None
+                };
+                accessor = Accessor::new("foo.service", get_connection_info);
+            }
+
+            match accessor.as_binder() {
+                Some(_) => {
+                    assert!(!deleted.load(Ordering::Relaxed));
+                }
+                None => panic!("failed to get that accessor binder"),
+            }
+        }
+        assert!(deleted.load(Ordering::Relaxed));
+    }
+
     #[tokio::test]
     async fn reassociate_rust_binder_async() {
         let service_name = "testing_service";
diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp
index fbab8f0..cac054e 100644
--- a/libs/binder/tests/parcel_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/Android.bp
@@ -39,6 +39,7 @@
             "smoreland@google.com",
             "waghpawan@google.com",
         ],
+        triage_assignee: "smoreland@google.com",
         use_for_presubmit: true,
     },
 
diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index e378b86..07f0143 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -25,6 +25,8 @@
 #include <binder/ParcelableHolder.h>
 #include <binder/PersistableBundle.h>
 #include <binder/Status.h>
+#include <fuzzbinder/random_binder.h>
+#include <fuzzbinder/random_fd.h>
 #include <utils/Flattenable.h>
 
 #include "../../Utils.h"
@@ -115,14 +117,6 @@
         p.setDataPosition(pos);
         FUZZ_LOG() << "setDataPosition done";
     },
-    [] (const ::android::Parcel& p, FuzzedDataProvider& provider) {
-        size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
-        std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len);
-        FUZZ_LOG() << "about to setData: " <<(bytes.data() ? HexString(bytes.data(), bytes.size()) : "null");
-        // TODO: allow all read and write operations
-        (*const_cast<::android::Parcel*>(&p)).setData(bytes.data(), bytes.size());
-        FUZZ_LOG() << "setData done";
-    },
     PARCEL_READ_NO_STATUS(size_t, allowFds),
     PARCEL_READ_NO_STATUS(size_t, hasFileDescriptors),
     PARCEL_READ_NO_STATUS(std::vector<android::sp<android::IBinder>>, debugReadAllStrongBinders),
@@ -404,5 +398,113 @@
         FUZZ_LOG() << " toString() result: " << toString;
     },
 };
+
+std::vector<ParcelWrite<::android::Parcel>> BINDER_PARCEL_WRITE_FUNCTIONS {
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call setDataSize";
+        size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
+        p.setDataSize(len);
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call setDataCapacity";
+        size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
+        p.setDataCapacity(len);
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call setData";
+        size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
+        std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len);
+        p.setData(bytes.data(), bytes.size());
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+        FUZZ_LOG() << "about to call appendFrom";
+
+        std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(provider.ConsumeIntegralInRange<size_t>(0, 4096));
+        ::android::Parcel p2;
+        fillRandomParcel(&p2, FuzzedDataProvider(bytes.data(), bytes.size()), options);
+
+        int32_t start = provider.ConsumeIntegral<int32_t>();
+        int32_t len = provider.ConsumeIntegral<int32_t>();
+        p.appendFrom(&p2, start, len);
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call pushAllowFds";
+        bool val = provider.ConsumeBool();
+        p.pushAllowFds(val);
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call restoreAllowFds";
+        bool val = provider.ConsumeBool();
+        p.restoreAllowFds(val);
+    },
+    // markForBinder - covered by fillRandomParcel, aborts if called multiple times
+    // markForRpc - covered by fillRandomParcel, aborts if called multiple times
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call writeInterfaceToken";
+        std::string interface = provider.ConsumeRandomLengthString();
+        p.writeInterfaceToken(android::String16(interface.c_str()));
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call setEnforceNoDataAvail";
+        p.setEnforceNoDataAvail(provider.ConsumeBool());
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call setServiceFuzzing";
+        p.setServiceFuzzing();
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call freeData";
+        p.freeData();
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call write";
+        size_t len = provider.ConsumeIntegralInRange<size_t>(0, 256);
+        std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len);
+        p.write(bytes.data(), bytes.size());
+    },
+    // write* - write functions all implemented by calling 'write' itself.
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+        FUZZ_LOG() << "about to call writeStrongBinder";
+
+        // TODO: this logic is somewhat duplicated with random parcel
+       android::sp<android::IBinder> binder;
+       if (provider.ConsumeBool() && options->extraBinders.size() > 0) {
+            binder = options->extraBinders.at(
+                    provider.ConsumeIntegralInRange<size_t>(0, options->extraBinders.size() - 1));
+        } else {
+            binder = android::getRandomBinder(&provider);
+            options->extraBinders.push_back(binder);
+        }
+
+        p.writeStrongBinder(binder);
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call writeFileDescriptor (no ownership)";
+        p.writeFileDescriptor(STDERR_FILENO, false /* takeOwnership */);
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+        FUZZ_LOG() << "about to call writeFileDescriptor (take ownership)";
+        std::vector<unique_fd> fds = android::getRandomFds(&provider);
+        if (fds.size() == 0) return;
+
+        p.writeDupFileDescriptor(fds.at(0).get());
+        options->extraFds.insert(options->extraFds.end(),
+             std::make_move_iterator(fds.begin() + 1),
+             std::make_move_iterator(fds.end()));
+    },
+    // TODO: writeBlob
+    // TODO: writeDupImmutableBlobFileDescriptor
+    // TODO: writeObject (or make the API private more likely)
+    [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call writeNoException";
+        p.writeNoException();
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call replaceCallingWorkSourceUid";
+        uid_t uid = provider.ConsumeIntegral<uid_t>();
+        p.replaceCallingWorkSourceUid(uid);
+    },
+};
+
 // clang-format on
 #pragma clang diagnostic pop
diff --git a/libs/binder/tests/parcel_fuzzer/binder.h b/libs/binder/tests/parcel_fuzzer/binder.h
index 0c51d68..b0ac140 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.h
+++ b/libs/binder/tests/parcel_fuzzer/binder.h
@@ -21,3 +21,4 @@
 #include "parcel_fuzzer.h"
 
 extern std::vector<ParcelRead<::android::Parcel>> BINDER_PARCEL_READ_FUNCTIONS;
+extern std::vector<ParcelWrite<::android::Parcel>> BINDER_PARCEL_WRITE_FUNCTIONS;
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
index e3a3371..3f8d71d 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
@@ -20,8 +20,11 @@
 #include "aidl/parcelables/GenericDataParcelable.h"
 #include "aidl/parcelables/SingleDataParcelable.h"
 
+#include <android/binder_libbinder.h>
 #include <android/binder_parcel_utils.h>
 #include <android/binder_parcelable_utils.h>
+#include <fuzzbinder/random_binder.h>
+#include <fuzzbinder/random_fd.h>
 
 #include "util.h"
 
@@ -211,16 +214,51 @@
             binder_status_t status = AParcel_marshal(p.aParcel(), buffer, start, len);
             FUZZ_LOG() << "status: " << status;
         },
-        [](const NdkParcelAdapter& /*p*/, FuzzedDataProvider& provider) {
-            FUZZ_LOG() << "about to unmarshal AParcel";
+};
+std::vector<ParcelWrite<NdkParcelAdapter>> BINDER_NDK_PARCEL_WRITE_FUNCTIONS{
+        [] (NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+            FUZZ_LOG() << "about to call AParcel_writeStrongBinder";
+
+            // TODO: this logic is somewhat duplicated with random parcel
+            android::sp<android::IBinder> binder;
+            if (provider.ConsumeBool() && options->extraBinders.size() > 0) {
+                binder = options->extraBinders.at(
+                        provider.ConsumeIntegralInRange<size_t>(0, options->extraBinders.size() - 1));
+            } else {
+                binder = android::getRandomBinder(&provider);
+                options->extraBinders.push_back(binder);
+            }
+
+            ndk::SpAIBinder abinder = ndk::SpAIBinder(AIBinder_fromPlatformBinder(binder));
+            AParcel_writeStrongBinder(p.aParcel(), abinder.get());
+        },
+        [] (NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+            FUZZ_LOG() << "about to call AParcel_writeParcelFileDescriptor";
+
+            auto fds = android::getRandomFds(&provider);
+            if (fds.size() == 0) return;
+
+            AParcel_writeParcelFileDescriptor(p.aParcel(), fds.at(0).get());
+            options->extraFds.insert(options->extraFds.end(),
+                 std::make_move_iterator(fds.begin() + 1),
+                 std::make_move_iterator(fds.end()));
+        },
+        // all possible data writes can be done as a series of 4-byte reads
+        [] (NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+            FUZZ_LOG() << "about to call AParcel_writeInt32";
+            int32_t val = provider.ConsumeIntegral<int32_t>();
+            AParcel_writeInt32(p.aParcel(), val);
+        },
+        [] (NdkParcelAdapter& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+            FUZZ_LOG() << "about to call AParcel_reset";
+            AParcel_reset(p.aParcel());
+        },
+        [](NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+            FUZZ_LOG() << "about to call AParcel_unmarshal";
             size_t len = provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes());
-            std::vector<uint8_t> parcelData = provider.ConsumeBytes<uint8_t>(len);
-            const uint8_t* buffer = parcelData.data();
-            const size_t bufferLen = parcelData.size();
-            NdkParcelAdapter adapter;
-            binder_status_t status = AParcel_unmarshal(adapter.aParcel(), buffer, bufferLen);
+            std::vector<uint8_t> data = provider.ConsumeBytes<uint8_t>(len);
+            binder_status_t status = AParcel_unmarshal(p.aParcel(), data.data(), data.size());
             FUZZ_LOG() << "status: " << status;
         },
-
 };
 // clang-format on
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.h b/libs/binder/tests/parcel_fuzzer/binder_ndk.h
index d19f25b..0c8b725 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.h
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.h
@@ -50,3 +50,4 @@
 };
 
 extern std::vector<ParcelRead<NdkParcelAdapter>> BINDER_NDK_PARCEL_READ_FUNCTIONS;
+extern std::vector<ParcelWrite<NdkParcelAdapter>> BINDER_NDK_PARCEL_WRITE_FUNCTIONS;
diff --git a/libs/binder/tests/parcel_fuzzer/main.cpp b/libs/binder/tests/parcel_fuzzer/main.cpp
index a57d07f..192f9d5 100644
--- a/libs/binder/tests/parcel_fuzzer/main.cpp
+++ b/libs/binder/tests/parcel_fuzzer/main.cpp
@@ -80,6 +80,7 @@
     (void)binder->transact(code, data, &reply, flag);
 }
 
+// start with a Parcel full of data (e.g. like you get from another process)
 template <typename P>
 void doReadFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads,
                 FuzzedDataProvider&& provider) {
@@ -95,10 +96,10 @@
     RandomParcelOptions options;
 
     P p;
-    fillRandomParcel(&p, std::move(provider), &options);
+    fillRandomParcel(&p, std::move(provider), &options); // consumes provider
 
     // since we are only using a byte to index
-    CHECK(reads.size() <= 255) << reads.size();
+    CHECK_LE(reads.size(), 255u) << reads.size();
 
     FUZZ_LOG() << "backend: " << backend;
     FUZZ_LOG() << "input: " << HexString(p.data(), p.dataSize());
@@ -115,26 +116,29 @@
     }
 }
 
-// Append two random parcels.
 template <typename P>
-void doAppendFuzz(const char* backend, FuzzedDataProvider&& provider) {
-    int32_t start = provider.ConsumeIntegral<int32_t>();
-    int32_t len = provider.ConsumeIntegral<int32_t>();
-
-    std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(
-            provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes()));
-
-    // same options so that FDs and binders could be shared in both Parcels
+void doReadWriteFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads,
+                     const std::vector<ParcelWrite<P>>& writes, FuzzedDataProvider&& provider) {
     RandomParcelOptions options;
+    P p;
 
-    P p0, p1;
-    fillRandomParcel(&p0, FuzzedDataProvider(bytes.data(), bytes.size()), &options);
-    fillRandomParcel(&p1, std::move(provider), &options);
+    // since we are only using a byte to index
+    CHECK_LE(reads.size() + writes.size(), 255u) << reads.size();
 
     FUZZ_LOG() << "backend: " << backend;
-    FUZZ_LOG() << "start: " << start << " len: " << len;
 
-    p0.appendFrom(&p1, start, len);
+    while (provider.remaining_bytes() > 0) {
+        uint8_t idx = provider.ConsumeIntegralInRange<uint8_t>(0, reads.size() + writes.size() - 1);
+
+        FUZZ_LOG() << "Instruction " << idx << " avail: " << p.dataAvail()
+                   << " pos: " << p.dataPosition() << " cap: " << p.dataCapacity();
+
+        if (idx < reads.size()) {
+            reads.at(idx)(p, provider);
+        } else {
+            writes.at(idx - reads.size())(p, provider, &options);
+        }
+    }
 }
 
 void* NothingClass_onCreate(void* args) {
@@ -156,7 +160,7 @@
 
     FuzzedDataProvider provider = FuzzedDataProvider(data, size);
 
-    const std::function<void(FuzzedDataProvider &&)> fuzzBackend[] = {
+    const std::function<void(FuzzedDataProvider&&)> fuzzBackend[] = {
             [](FuzzedDataProvider&& provider) {
                 doTransactFuzz<
                         ::android::hardware::Parcel>("hwbinder",
@@ -187,10 +191,14 @@
                                              std::move(provider));
             },
             [](FuzzedDataProvider&& provider) {
-                doAppendFuzz<::android::Parcel>("binder", std::move(provider));
+                doReadWriteFuzz<::android::Parcel>("binder", BINDER_PARCEL_READ_FUNCTIONS,
+                                                   BINDER_PARCEL_WRITE_FUNCTIONS,
+                                                   std::move(provider));
             },
             [](FuzzedDataProvider&& provider) {
-                doAppendFuzz<NdkParcelAdapter>("binder_ndk", std::move(provider));
+                doReadWriteFuzz<NdkParcelAdapter>("binder_ndk", BINDER_NDK_PARCEL_READ_FUNCTIONS,
+                                                  BINDER_NDK_PARCEL_WRITE_FUNCTIONS,
+                                                  std::move(provider));
             },
     };
 
diff --git a/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h b/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h
index 765a93e..dbd0cae 100644
--- a/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h
+++ b/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h
@@ -15,9 +15,13 @@
  */
 #pragma once
 
+#include <fuzzbinder/random_parcel.h>
 #include <fuzzer/FuzzedDataProvider.h>
 
 #include <functional>
 
 template <typename P>
 using ParcelRead = std::function<void(const P& p, FuzzedDataProvider& provider)>;
+template <typename P>
+using ParcelWrite = std::function<void(P& p, FuzzedDataProvider& provider,
+                                       android::RandomParcelOptions* options)>;
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 60fb00e..701fb43 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -207,3 +207,10 @@
   description: "Allow user to enable key repeats or configure timeout before key repeat and key repeat delay rates."
   bug: "336585002"
 }
+
+flag {
+  name: "rotary_input_telemetry"
+  namespace: "wear_frameworks"
+  description: "Enable telemetry for rotary input"
+  bug: "370353565"
+}
diff --git a/services/inputflinger/InputThread.cpp b/services/inputflinger/InputThread.cpp
index bd4b192..7cf4e39 100644
--- a/services/inputflinger/InputThread.cpp
+++ b/services/inputflinger/InputThread.cpp
@@ -26,6 +26,16 @@
 
 namespace {
 
+bool applyInputEventProfile(const Thread& thread) {
+#if defined(__ANDROID__)
+    return SetTaskProfiles(thread.getTid(), {"InputPolicy"});
+#else
+    // Since thread information is not available and there's no benefit of
+    // applying the task profile on host, return directly.
+    return true;
+#endif
+}
+
 // Implementation of Thread from libutils.
 class InputThreadImpl : public Thread {
 public:
@@ -47,11 +57,11 @@
 
 InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake,
                          bool isInCriticalPath)
-      : mName(name), mThreadWake(wake) {
+      : mThreadWake(wake) {
     mThread = sp<InputThreadImpl>::make(loop);
-    mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
+    mThread->run(name.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
     if (input_flags::enable_input_policy_profile() && isInCriticalPath) {
-        if (!applyInputEventProfile()) {
+        if (!applyInputEventProfile(*mThread)) {
             LOG(ERROR) << "Couldn't apply input policy profile for " << name;
         }
     }
@@ -75,14 +85,4 @@
 #endif
 }
 
-bool InputThread::applyInputEventProfile() {
-#if defined(__ANDROID__)
-    return SetTaskProfiles(mThread->getTid(), {"InputPolicy"});
-#else
-    // Since thread information is not available and there's no benefit of
-    // applying the task profile on host, return directly.
-    return true;
-#endif
-}
-
 } // namespace android
diff --git a/services/inputflinger/include/InputThread.h b/services/inputflinger/include/InputThread.h
index 595f5a2..ed92b8f 100644
--- a/services/inputflinger/include/InputThread.h
+++ b/services/inputflinger/include/InputThread.h
@@ -35,10 +35,8 @@
     bool isCallingThread();
 
 private:
-    std::string mName;
     std::function<void()> mThreadWake;
     sp<Thread> mThread;
-    bool applyInputEventProfile();
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index b76e8c5..b3cd35c 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -90,10 +90,14 @@
         "libstatslog",
         "libstatspull",
         "libutils",
+        "libstatssocket",
     ],
     static_libs: [
         "libchrome-gestures",
         "libui-types",
+        "libexpresslog",
+        "libtextclassifier_hash_static",
+        "libstatslog_express",
     ],
     header_libs: [
         "libbatteryservice_headers",
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index b72cc6e..c633b49 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -20,6 +20,8 @@
 
 #include "RotaryEncoderInputMapper.h"
 
+#include <Counter.h>
+#include <com_android_input_flags.h>
 #include <utils/Timers.h>
 #include <optional>
 
@@ -27,14 +29,26 @@
 
 namespace android {
 
+using android::expresslog::Counter;
+
+constexpr float kDefaultResolution = 0;
 constexpr float kDefaultScaleFactor = 1.0f;
+constexpr int32_t kDefaultMinRotationsToLog = 3;
 
 RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
                                                    const InputReaderConfiguration& readerConfig)
+      : RotaryEncoderInputMapper(deviceContext, readerConfig,
+                                 Counter::logIncrement /* telemetryLogCounter */) {}
+
+RotaryEncoderInputMapper::RotaryEncoderInputMapper(
+        InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig,
+        std::function<void(const char*, int64_t)> telemetryLogCounter)
       : InputMapper(deviceContext, readerConfig),
         mSource(AINPUT_SOURCE_ROTARY_ENCODER),
         mScalingFactor(kDefaultScaleFactor),
-        mOrientation(ui::ROTATION_0) {}
+        mResolution(kDefaultResolution),
+        mOrientation(ui::ROTATION_0),
+        mTelemetryLogCounter(telemetryLogCounter) {}
 
 RotaryEncoderInputMapper::~RotaryEncoderInputMapper() {}
 
@@ -51,6 +65,7 @@
         if (!res.has_value()) {
             ALOGW("Rotary Encoder device configuration file didn't specify resolution!\n");
         }
+        mResolution = res.value_or(kDefaultResolution);
         std::optional<float> scalingFactor = config.getFloat("device.scalingFactor");
         if (!scalingFactor.has_value()) {
             ALOGW("Rotary Encoder device configuration file didn't specify scaling factor,"
@@ -59,7 +74,22 @@
         }
         mScalingFactor = scalingFactor.value_or(kDefaultScaleFactor);
         info.addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
-                            res.value_or(0.0f) * mScalingFactor);
+                            mResolution * mScalingFactor);
+
+        if (com::android::input::flags::rotary_input_telemetry()) {
+            mMinRotationsToLog = config.getInt("rotary_encoder.min_rotations_to_log");
+            if (!mMinRotationsToLog.has_value()) {
+                ALOGI("Rotary Encoder device configuration file didn't specify min log rotation.");
+            } else if (*mMinRotationsToLog <= 0) {
+                ALOGE("Rotary Encoder device configuration specified non-positive min log rotation "
+                      ": %d. Telemetry logging of rotations disabled.",
+                      *mMinRotationsToLog);
+                mMinRotationsToLog = {};
+            } else {
+                ALOGD("Rotary Encoder telemetry enabled. mMinRotationsToLog=%d",
+                      *mMinRotationsToLog);
+            }
+        }
     }
 }
 
@@ -121,10 +151,29 @@
     return out;
 }
 
+void RotaryEncoderInputMapper::logScroll(float scroll) {
+    if (mResolution <= 0 || !mMinRotationsToLog) return;
+
+    mUnloggedScrolls += fabs(scroll);
+
+    // unitsPerRotation = (2 * PI * radians) * (units per radian (i.e. resolution))
+    const float unitsPerRotation = 2 * M_PI * mResolution;
+    const float scrollsPerMinRotationsToLog = *mMinRotationsToLog * unitsPerRotation;
+    const int32_t numMinRotationsToLog =
+            static_cast<int32_t>(mUnloggedScrolls / scrollsPerMinRotationsToLog);
+    mUnloggedScrolls = std::fmod(mUnloggedScrolls, scrollsPerMinRotationsToLog);
+    if (numMinRotationsToLog) {
+        mTelemetryLogCounter("input.value_rotary_input_device_full_rotation_count",
+                             numMinRotationsToLog * (*mMinRotationsToLog));
+    }
+}
+
 std::list<NotifyArgs> RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
 
     float scroll = mRotaryEncoderScrollAccumulator.getRelativeVWheel();
+    logScroll(scroll);
+
     if (mSlopController) {
         scroll = mSlopController->consumeEvent(when, scroll);
     }
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index 7e80415..d74ced1 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -46,13 +46,39 @@
 
     int32_t mSource;
     float mScalingFactor;
+    /** Units per rotation, provided via the `device.res` IDC property. */
+    float mResolution;
     ui::Rotation mOrientation;
+    /**
+     * The minimum number of rotations to log for telemetry.
+     * Provided via `rotary_encoder.min_rotations_to_log` IDC property. If no value is provided in
+     * the IDC file, or if a non-positive value is provided, the telemetry will be disabled, and
+     * this value is set to the empty optional.
+     */
+    std::optional<int32_t> mMinRotationsToLog;
+    /**
+     * A function to log count for telemetry.
+     * The char* is the logging key, and the int64_t is the value to log.
+     * Abstracting the actual logging APIs via this function is helpful for simple unit testing.
+     */
+    std::function<void(const char*, int64_t)> mTelemetryLogCounter;
     ui::LogicalDisplayId mDisplayId = ui::LogicalDisplayId::INVALID;
     std::unique_ptr<SlopController> mSlopController;
 
+    /** Amount of raw scrolls (pre-slop) not yet logged for telemetry. */
+    float mUnloggedScrolls = 0;
+
     explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
                                       const InputReaderConfiguration& readerConfig);
+
+    /** This is a test constructor that allows injecting the expresslog Counter logic. */
+    RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
+                             const InputReaderConfiguration& readerConfig,
+                             std::function<void(const char*, int64_t)> expressLogCounter);
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
+
+    /** Logs a given amount of scroll for telemetry. */
+    void logScroll(float scroll);
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
index 6607bc7..486d893 100644
--- a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
+++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
@@ -23,6 +23,8 @@
 
 #include <android-base/logging.h>
 #include <android_companion_virtualdevice_flags.h>
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <gtest/gtest.h>
 #include <input/DisplayViewport.h>
 #include <linux/input-event-codes.h>
@@ -100,6 +102,15 @@
         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
                 .WillRepeatedly(Return(false));
     }
+
+    std::map<const char*, int64_t> mTelemetryLogCounts;
+
+    /**
+     * A fake function for telemetry logging.
+     * Records the log counts in the `mTelemetryLogCounts` map.
+     */
+    std::function<void(const char*, int64_t)> mTelemetryLogCounter =
+            [this](const char* key, int64_t value) { mTelemetryLogCounts[key] += value; };
 };
 
 TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdWithAssociatedViewport) {
@@ -187,4 +198,142 @@
                               WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(0.5f)))));
 }
 
+TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, RotaryInputTelemetryFlagOff_NoRotationLogging,
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                       rotary_input_telemetry))) {
+    mPropertyMap.addProperty("device.res", "3");
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                          mTelemetryLogCounter);
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 70);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+}
+
+TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, ZeroResolution_NoRotationLogging,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                      rotary_input_telemetry))) {
+    mPropertyMap.addProperty("device.res", "-3");
+    mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "2");
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                          mTelemetryLogCounter);
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+}
+
+TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, NegativeMinLogRotation_NoRotationLogging,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                      rotary_input_telemetry))) {
+    mPropertyMap.addProperty("device.res", "3");
+    mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "-2");
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                          mTelemetryLogCounter);
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+}
+
+TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, ZeroMinLogRotation_NoRotationLogging,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                      rotary_input_telemetry))) {
+    mPropertyMap.addProperty("device.res", "3");
+    mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "0");
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                          mTelemetryLogCounter);
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+}
+
+TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, NoMinLogRotation_NoRotationLogging,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                      rotary_input_telemetry))) {
+    // 3 units per radian, 2 * M_PI * 3 = ~18.85 units per rotation.
+    mPropertyMap.addProperty("device.res", "3");
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                          mTelemetryLogCounter);
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+}
+
+TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, RotationLogging,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                      rotary_input_telemetry))) {
+    // 3 units per radian, 2 * M_PI * 3 = ~18.85 units per rotation.
+    // Multiples of `unitsPerRoation`, to easily follow the assertions below.
+    // [18.85, 37.7, 56.55, 75.4, 94.25, 113.1, 131.95, 150.8]
+    mPropertyMap.addProperty("device.res", "3");
+    mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "2");
+
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                          mTelemetryLogCounter);
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 15); // total scroll = 15
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 13); // total scroll = 28
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    // Expect 0 since `min_rotations_to_log` = 2, and total scroll 28 only has 1 rotation.
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 10); // total scroll = 38
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    // Total scroll includes >= `min_rotations_to_log` (2), expect log.
+    ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 2);
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, -22); // total scroll = 60
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    // Expect no additional telemetry. Total rotation is 3, and total unlogged rotation is 1, which
+    // is less than `min_rotations_to_log`.
+    ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 2);
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, -16); // total scroll = 76
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    // Total unlogged rotation >= `min_rotations_to_log` (2), so expect 2 more logged rotation.
+    ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 4);
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, -76); // total scroll = 152
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    // Total unlogged scroll >= 4*`min_rotations_to_log`. Expect *all* unlogged rotations to be
+    // logged, even if that's more than multiple of `min_rotations_to_log`.
+    ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 8);
+}
+
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/FrontEnd/LayerHandle.cpp b/services/surfaceflinger/FrontEnd/LayerHandle.cpp
index 75e4e3a..ffd51a4 100644
--- a/services/surfaceflinger/FrontEnd/LayerHandle.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHandle.cpp
@@ -28,7 +28,7 @@
 
 LayerHandle::~LayerHandle() {
     if (mFlinger) {
-        mFlinger->onHandleDestroyed(this, mLayer, mLayerId);
+        mFlinger->onHandleDestroyed(mLayer, mLayerId);
     }
 }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 06560d0..8d16b9f 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -952,7 +952,7 @@
                 // Do not wait the future to avoid deadlocks
                 // between main and Perfetto threads (b/313130597)
                 static_cast<void>(mScheduler->schedule(
-                        [&, onLayersSnapshot]() FTL_FAKE_GUARD(mStateLock)
+                        [&, traceFlags, onLayersSnapshot]() FTL_FAKE_GUARD(mStateLock)
                                 FTL_FAKE_GUARD(kMainThreadContext) {
                                     auto snapshot =
                                             takeLayersSnapshotProto(traceFlags, TimePoint::now(),
@@ -4723,6 +4723,7 @@
     for (auto& state : states) {
         resolvedStates.emplace_back(std::move(state));
         auto& resolvedState = resolvedStates.back();
+        resolvedState.layerId = LayerHandle::getLayerId(resolvedState.state.surface);
         if (resolvedState.state.hasBufferChanges() && resolvedState.state.hasValidBuffer() &&
             resolvedState.state.surface) {
             sp<Layer> layer = LayerHandle::getLayer(resolvedState.state.surface);
@@ -4734,9 +4735,8 @@
             if (resolvedState.externalTexture) {
                 resolvedState.state.bufferData->buffer = resolvedState.externalTexture->getBuffer();
             }
-            mBufferCountTracker.increment(resolvedState.state.surface->localBinder());
+            mBufferCountTracker.increment(resolvedState.layerId);
         }
-        resolvedState.layerId = LayerHandle::getLayerId(resolvedState.state.surface);
         if (resolvedState.state.what & layer_state_t::eReparent) {
             resolvedState.parentId =
                     getLayerIdFromSurfaceControl(resolvedState.state.parentSurfaceControlForChild);
@@ -5179,7 +5179,7 @@
             std::atomic<int32_t>* pendingBufferCounter = layer->getPendingBufferCounter();
             if (pendingBufferCounter) {
                 std::string counterName = layer->getPendingBufferCounterName();
-                mBufferCountTracker.add(outResult.handle->localBinder(), counterName,
+                mBufferCountTracker.add(LayerHandle::getLayerId(outResult.handle), counterName,
                                         pendingBufferCounter);
             }
         } break;
@@ -5227,7 +5227,7 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId) {
+void SurfaceFlinger::onHandleDestroyed(sp<Layer>& layer, uint32_t layerId) {
     {
         // Used to remove stalled transactions which uses an internal lock.
         ftl::FakeGuard guard(kMainThreadContext);
@@ -5240,7 +5240,7 @@
 
     Mutex::Autolock stateLock(mStateLock);
     layer->onHandleDestroyed();
-    mBufferCountTracker.remove(handle);
+    mBufferCountTracker.remove(layerId);
     layer.clear();
     setTransactionFlags(eTransactionFlushNeeded | eTransactionNeeded);
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 3eb72cc..db0e15e 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -296,7 +296,7 @@
     // Called when all clients have released all their references to
     // this layer. The layer may still be kept alive by its parents but
     // the client can no longer modify this layer directly.
-    void onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId);
+    void onHandleDestroyed(sp<Layer>& layer, uint32_t layerId);
 
     TransactionCallbackInvoker& getTransactionCallbackInvoker() {
         return mTransactionCallbackInvoker;
@@ -433,32 +433,32 @@
     // This is done to avoid lock contention with the main thread.
     class BufferCountTracker {
     public:
-        void increment(BBinder* layerHandle) {
+        void increment(uint32_t layerId) {
             std::lock_guard<std::mutex> lock(mLock);
-            auto it = mCounterByLayerHandle.find(layerHandle);
-            if (it != mCounterByLayerHandle.end()) {
+            auto it = mCounterByLayerId.find(layerId);
+            if (it != mCounterByLayerId.end()) {
                 auto [name, pendingBuffers] = it->second;
                 int32_t count = ++(*pendingBuffers);
                 SFTRACE_INT(name.c_str(), count);
             } else {
-                ALOGW("Handle not found! %p", layerHandle);
+                ALOGW("Layer ID not found! %d", layerId);
             }
         }
 
-        void add(BBinder* layerHandle, const std::string& name, std::atomic<int32_t>* counter) {
+        void add(uint32_t layerId, const std::string& name, std::atomic<int32_t>* counter) {
             std::lock_guard<std::mutex> lock(mLock);
-            mCounterByLayerHandle[layerHandle] = std::make_pair(name, counter);
+            mCounterByLayerId[layerId] = std::make_pair(name, counter);
         }
 
-        void remove(BBinder* layerHandle) {
+        void remove(uint32_t layerId) {
             std::lock_guard<std::mutex> lock(mLock);
-            mCounterByLayerHandle.erase(layerHandle);
+            mCounterByLayerId.erase(layerId);
         }
 
     private:
         std::mutex mLock;
-        std::unordered_map<BBinder*, std::pair<std::string, std::atomic<int32_t>*>>
-                mCounterByLayerHandle GUARDED_BY(mLock);
+        std::unordered_map<uint32_t, std::pair<std::string, std::atomic<int32_t>*>>
+                mCounterByLayerId GUARDED_BY(mLock);
     };
 
     enum class BootStage {
diff --git a/services/surfaceflinger/tests/benchmarks/Android.bp b/services/surfaceflinger/tests/benchmarks/Android.bp
index 1c47be34..22fca08 100644
--- a/services/surfaceflinger/tests/benchmarks/Android.bp
+++ b/services/surfaceflinger/tests/benchmarks/Android.bp
@@ -22,7 +22,6 @@
     static_libs: [
         "libgmock",
         "libgtest",
-        "libc++fs",
     ],
     header_libs: [
         "libsurfaceflinger_mocks_headers",
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index c043b88..4dec5f6 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -637,7 +637,7 @@
     void destroyAllLayerHandles() {
         ftl::FakeGuard guard(kMainThreadContext);
         for (auto [layerId, legacyLayer] : mFlinger->mLegacyLayers) {
-            mFlinger->onHandleDestroyed(nullptr, legacyLayer, layerId);
+            mFlinger->onHandleDestroyed(legacyLayer, layerId);
         }
     }