Merge "Allow dispatcher to be idle after metrics push" into main
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index b22cc2a..372008e 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -118,6 +118,12 @@
     ],
 }
 
+prebuilt_etc {
+    name: "default_screenshot",
+    src: "res/default_screenshot.png",
+    filename_from_src: true,
+}
+
 cc_binary {
     name: "dumpstate",
     defaults: ["dumpstate_defaults"],
@@ -130,6 +136,7 @@
     required: [
         "atrace",
         "bugreport_procdump",
+        "default_screenshot",
         "dmabuf_dump",
         "ip",
         "iptables",
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index d427ecf..d24edc4 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -206,6 +206,9 @@
 static const std::string SHUTDOWN_CHECKPOINTS_DIR = "/data/system/shutdown-checkpoints/";
 static const std::string SHUTDOWN_CHECKPOINTS_FILE_PREFIX = "checkpoints-";
 
+// File path to default screenshot image, that used when failed to capture the real screenshot.
+static const std::string DEFAULT_SCREENSHOT_PATH = "/system/etc/default_screenshot.png";
+
 // TODO: temporary variables and functions used during C++ refactoring
 
 #define RETURN_IF_USER_DENIED_CONSENT()                                                        \
@@ -765,10 +768,14 @@
 
     bool copy_succeeded = android::os::CopyFileToFd(ds.screenshot_path_,
                                                     ds.options_->screenshot_fd.get());
-    ds.options_->is_screenshot_copied = copy_succeeded;
     if (copy_succeeded) {
         android::os::UnlinkAndLogOnError(ds.screenshot_path_);
+    } else {
+        MYLOGE("Failed to copy screenshot to a permanent file.\n");
+        copy_succeeded = android::os::CopyFileToFd(DEFAULT_SCREENSHOT_PATH,
+                                                           ds.options_->screenshot_fd.get());
     }
+    ds.options_->is_screenshot_copied = copy_succeeded;
     return android::binder::Status::ok();
 }
 
@@ -3442,7 +3449,9 @@
             // Do an early return if there were errors. We make an exception for consent
             // timing out because it's possible the user got distracted. In this case the
             // bugreport is not shared but made available for manual retrieval.
-            MYLOGI("User denied consent. Returning\n");
+            MYLOGI("Bug report generation failed, this could have been due to"
+                   " several reasons such as BR copy failed, user consent was"
+                   " not grated etc. Returning\n");
             return status;
         }
         if (status == Dumpstate::RunStatus::USER_CONSENT_TIMED_OUT) {
@@ -3729,12 +3738,16 @@
             if (options_->do_screenshot &&
                 options_->screenshot_fd.get() != -1 &&
                 !options_->is_screenshot_copied) {
-                copy_succeeded = android::os::CopyFileToFd(screenshot_path_,
+                bool is_screenshot_copied = android::os::CopyFileToFd(screenshot_path_,
                                                            options_->screenshot_fd.get());
-                options_->is_screenshot_copied = copy_succeeded;
-                if (copy_succeeded) {
+                if (is_screenshot_copied) {
                     android::os::UnlinkAndLogOnError(screenshot_path_);
+                } else {
+                    MYLOGE("Failed to copy screenshot to a permanent file.\n");
+                    is_screenshot_copied = android::os::CopyFileToFd(DEFAULT_SCREENSHOT_PATH,
+                                                           options_->screenshot_fd.get());
                 }
+                options_->is_screenshot_copied = is_screenshot_copied;
             }
         }
         return copy_succeeded ? Dumpstate::RunStatus::OK : Dumpstate::RunStatus::ERROR;
@@ -3825,7 +3838,7 @@
 DurationReporter::~DurationReporter() {
     if (!title_.empty()) {
         float elapsed = (float)(Nanotime() - started_) / NANOS_PER_SEC;
-        if (elapsed >= .5f || verbose_) {
+        if (elapsed >= 1.0f || verbose_) {
             MYLOGD("Duration of '%s': %.2fs\n", title_.c_str(), elapsed);
         }
         if (!logcat_only_) {
diff --git a/cmds/dumpstate/res/default_screenshot.png b/cmds/dumpstate/res/default_screenshot.png
new file mode 100644
index 0000000..10f36aa
--- /dev/null
+++ b/cmds/dumpstate/res/default_screenshot.png
Binary files differ
diff --git a/include/ftl/non_null.h b/include/ftl/non_null.h
index 4a5d8bf..e684288 100644
--- a/include/ftl/non_null.h
+++ b/include/ftl/non_null.h
@@ -68,6 +68,15 @@
   constexpr NonNull(const NonNull&) = default;
   constexpr NonNull& operator=(const NonNull&) = default;
 
+  template <typename U, typename = std::enable_if_t<std::is_convertible_v<U, Pointer>>>
+  constexpr NonNull(const NonNull<U>& other) : pointer_(other.get()) {}
+
+  template <typename U, typename = std::enable_if_t<std::is_convertible_v<U, Pointer>>>
+  constexpr NonNull& operator=(const NonNull<U>& other) {
+    pointer_ = other.get();
+    return *this;
+  }
+
   [[nodiscard]] constexpr const Pointer& get() const { return pointer_; }
   [[nodiscard]] constexpr explicit operator const Pointer&() const { return get(); }
 
diff --git a/include/input/InputEventBuilders.h b/include/input/InputEventBuilders.h
index 5bd5070..1899a66 100644
--- a/include/input/InputEventBuilders.h
+++ b/include/input/InputEventBuilders.h
@@ -21,6 +21,7 @@
 #include <input/Input.h>
 #include <input/InputTransport.h>
 #include <ui/LogicalDisplayId.h>
+#include <ui/Transform.h>
 #include <utils/Timers.h> // for nsecs_t, systemTime
 
 #include <vector>
@@ -94,16 +95,81 @@
         return *this;
     }
 
+    InputMessageBuilder& hmac(const std::array<uint8_t, 32>& hmac) {
+        mHmac = hmac;
+        return *this;
+    }
+
     InputMessageBuilder& action(int32_t action) {
         mAction = action;
         return *this;
     }
 
+    InputMessageBuilder& actionButton(int32_t actionButton) {
+        mActionButton = actionButton;
+        return *this;
+    }
+
+    InputMessageBuilder& flags(int32_t flags) {
+        mFlags = flags;
+        return *this;
+    }
+
+    InputMessageBuilder& metaState(int32_t metaState) {
+        mMetaState = metaState;
+        return *this;
+    }
+
+    InputMessageBuilder& buttonState(int32_t buttonState) {
+        mButtonState = buttonState;
+        return *this;
+    }
+
+    InputMessageBuilder& classification(MotionClassification classification) {
+        mClassification = classification;
+        return *this;
+    }
+
+    InputMessageBuilder& edgeFlags(int32_t edgeFlags) {
+        mEdgeFlags = edgeFlags;
+        return *this;
+    }
+
     InputMessageBuilder& downTime(nsecs_t downTime) {
         mDownTime = downTime;
         return *this;
     }
 
+    InputMessageBuilder& transform(const ui::Transform& transform) {
+        mTransform = transform;
+        return *this;
+    }
+
+    InputMessageBuilder& xPrecision(float xPrecision) {
+        mXPrecision = xPrecision;
+        return *this;
+    }
+
+    InputMessageBuilder& yPrecision(float yPrecision) {
+        mYPrecision = yPrecision;
+        return *this;
+    }
+
+    InputMessageBuilder& xCursorPosition(float xCursorPosition) {
+        mXCursorPosition = xCursorPosition;
+        return *this;
+    }
+
+    InputMessageBuilder& yCursorPosition(float yCursorPosition) {
+        mYCursorPosition = yCursorPosition;
+        return *this;
+    }
+
+    InputMessageBuilder& rawTransform(const ui::Transform& rawTransform) {
+        mRawTransform = rawTransform;
+        return *this;
+    }
+
     InputMessageBuilder& pointer(PointerBuilder pointerBuilder) {
         mPointers.push_back(pointerBuilder);
         return *this;
@@ -121,8 +187,30 @@
         message.body.motion.deviceId = mDeviceId;
         message.body.motion.source = mSource;
         message.body.motion.displayId = mDisplayId.val();
+        message.body.motion.hmac = std::move(mHmac);
         message.body.motion.action = mAction;
+        message.body.motion.actionButton = mActionButton;
+        message.body.motion.flags = mFlags;
+        message.body.motion.metaState = mMetaState;
+        message.body.motion.buttonState = mButtonState;
+        message.body.motion.edgeFlags = mEdgeFlags;
         message.body.motion.downTime = mDownTime;
+        message.body.motion.dsdx = mTransform.dsdx();
+        message.body.motion.dtdx = mTransform.dtdx();
+        message.body.motion.dtdy = mTransform.dtdy();
+        message.body.motion.dsdy = mTransform.dsdy();
+        message.body.motion.tx = mTransform.ty();
+        message.body.motion.ty = mTransform.tx();
+        message.body.motion.xPrecision = mXPrecision;
+        message.body.motion.yPrecision = mYPrecision;
+        message.body.motion.xCursorPosition = mXCursorPosition;
+        message.body.motion.yCursorPosition = mYCursorPosition;
+        message.body.motion.dsdxRaw = mRawTransform.dsdx();
+        message.body.motion.dtdxRaw = mRawTransform.dtdx();
+        message.body.motion.dtdyRaw = mRawTransform.dtdy();
+        message.body.motion.dsdyRaw = mRawTransform.dsdy();
+        message.body.motion.txRaw = mRawTransform.ty();
+        message.body.motion.tyRaw = mRawTransform.tx();
 
         for (size_t i = 0; i < mPointers.size(); ++i) {
             message.body.motion.pointers[i].properties = mPointers[i].buildProperties();
@@ -140,9 +228,21 @@
     DeviceId mDeviceId{DEFAULT_DEVICE_ID};
     int32_t mSource{AINPUT_SOURCE_TOUCHSCREEN};
     ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT};
+    std::array<uint8_t, 32> mHmac{INVALID_HMAC};
     int32_t mAction{AMOTION_EVENT_ACTION_MOVE};
+    int32_t mActionButton{0};
+    int32_t mFlags{0};
+    int32_t mMetaState{AMETA_NONE};
+    int32_t mButtonState{0};
+    MotionClassification mClassification{MotionClassification::NONE};
+    int32_t mEdgeFlags{0};
     nsecs_t mDownTime{mEventTime};
-
+    ui::Transform mTransform{};
+    float mXPrecision{1.0f};
+    float mYPrecision{1.0f};
+    float mXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+    float mYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+    ui::Transform mRawTransform{};
     std::vector<PointerBuilder> mPointers;
 };
 
diff --git a/include/input/Resampler.h b/include/input/Resampler.h
index 4aaeddd..da0c5b2 100644
--- a/include/input/Resampler.h
+++ b/include/input/Resampler.h
@@ -65,7 +65,8 @@
      * extrapolation takes place and `resampleTime` is too far in the future. If `futureSample` is
      * not null, interpolation will occur. If `futureSample` is null and there is enough historical
      * data, LegacyResampler will extrapolate. Otherwise, no resampling takes place and
-     * `motionEvent` is unmodified.
+     * `motionEvent` is unmodified. Furthermore, motionEvent is not resampled if resampleTime equals
+     * the last sample eventTime of motionEvent.
      */
     void resampleMotionEvent(std::chrono::nanoseconds frameTime, MotionEvent& motionEvent,
                              const InputMessage* futureSample) override;
diff --git a/libs/binder/Status.cpp b/libs/binder/Status.cpp
index dba6587..9a98097 100644
--- a/libs/binder/Status.cpp
+++ b/libs/binder/Status.cpp
@@ -99,27 +99,28 @@
         return status;
     }
 
+    if (mException == EX_HAS_NOTED_APPOPS_REPLY_HEADER) {
+        status = skipUnusedHeader(parcel);
+        if (status != OK) {
+            setFromStatusT(status);
+            return status;
+        }
+        // Read next exception code.
+        status = parcel.readInt32(&mException);
+        if (status != OK) {
+            setFromStatusT(status);
+            return status;
+        }
+    }
+
     // Skip over fat response headers.  Not used (or propagated) in native code.
     if (mException == EX_HAS_REPLY_HEADER) {
-        // Note that the header size includes the 4 byte size field.
-        const size_t header_start = parcel.dataPosition();
-        // Get available size before reading more
-        const size_t header_avail = parcel.dataAvail();
-
-        int32_t header_size;
-        status = parcel.readInt32(&header_size);
+        status = skipUnusedHeader(parcel);
         if (status != OK) {
             setFromStatusT(status);
             return status;
         }
 
-        if (header_size < 0 || static_cast<size_t>(header_size) > header_avail) {
-            android_errorWriteLog(0x534e4554, "132650049");
-            setFromStatusT(UNKNOWN_ERROR);
-            return UNKNOWN_ERROR;
-        }
-
-        parcel.setDataPosition(header_start + header_size);
         // And fat response headers are currently only used when there are no
         // exceptions, so act like there was no error.
         mException = EX_NONE;
@@ -257,5 +258,28 @@
     return ret;
 }
 
+status_t Status::skipUnusedHeader(const Parcel& parcel) {
+    // Note that the header size includes the 4 byte size field.
+    const size_t header_start = parcel.dataPosition();
+    // Get available size before reading more
+    const size_t header_avail = parcel.dataAvail();
+
+    int32_t header_size;
+    status_t status = parcel.readInt32(&header_size);
+    ALOGD("Skip unused header. exception code: %d, start: %zu, size: %d.",
+           mException, header_start, header_size);
+    if (status != OK) {
+        return status;
+    }
+
+    if (header_size < 0 || static_cast<size_t>(header_size) > header_avail) {
+        android_errorWriteLog(0x534e4554, "132650049");
+        return UNKNOWN_ERROR;
+    }
+
+    parcel.setDataPosition(header_start + header_size);
+    return OK;
+}
+
 }  // namespace binder
 }  // namespace android
diff --git a/libs/binder/include/binder/Status.h b/libs/binder/include/binder/Status.h
index 49ccf7c..d69f662 100644
--- a/libs/binder/include/binder/Status.h
+++ b/libs/binder/include/binder/Status.h
@@ -67,6 +67,9 @@
         EX_SERVICE_SPECIFIC = -8,
         EX_PARCELABLE = -9,
 
+        // See android/os/Parcel.java. We need to handle this in native code.
+        EX_HAS_NOTED_APPOPS_REPLY_HEADER = -127,
+
         // This is special and Java specific; see Parcel.java.
         EX_HAS_REPLY_HEADER = -128,
         // This is special, and indicates to C++ binder proxies that the
@@ -150,6 +153,8 @@
     Status(int32_t exceptionCode, int32_t errorCode);
     Status(int32_t exceptionCode, int32_t errorCode, const String8& message);
 
+    status_t skipUnusedHeader(const Parcel& parcel);
+
     // If |mException| == EX_TRANSACTION_FAILED, generated code will return
     // |mErrorCode| as the result of the transaction rather than write an
     // exception to the reply parcel.
diff --git a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
index 8296356..9d68399 100644
--- a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
@@ -30,7 +30,7 @@
 #include <android/binder_auto_utils.h>
 #include <android/binder_ibinder.h>
 
-#if defined(__ANDROID_VENDOR__)
+#if defined(__ANDROID_VENDOR_API__)
 #include <android/llndk-versioning.h>
 #elif !defined(API_LEVEL_AT_LEAST)
 #if defined(__BIONIC__)
@@ -39,7 +39,7 @@
 #else
 #define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) (true)
 #endif  // __BIONIC__
-#endif  // __ANDROID_VENDOR__
+#endif  // __ANDROID_VENDOR_API__
 
 #if __has_include(<android/binder_shell.h>)
 #include <android/binder_shell.h>
@@ -297,9 +297,7 @@
     }
 #endif
 
-// TODO(b/368559337): fix versioning on product partition
-#if !defined(__ANDROID_PRODUCT__) && \
-        (defined(__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__) || __ANDROID_API__ >= 36)
+#if defined(__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__) || __ANDROID_API__ >= 36
     if API_LEVEL_AT_LEAST (36, 202504) {
         if (codeToFunction != nullptr) {
             AIBinder_Class_setTransactionCodeToFunctionNameMap(clazz, codeToFunction,
diff --git a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
index c1d0e9f..83976b3 100644
--- a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
+++ b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
@@ -22,8 +22,8 @@
 #include <set>
 #include <sstream>
 
-// Include llndk-versioning.h only for vendor build as it is not available for NDK headers.
-#if defined(__ANDROID_VENDOR__)
+// Include llndk-versioning.h only for non-system build as it is not available for NDK headers.
+#if defined(__ANDROID_VENDOR_API__)
 #include <android/llndk-versioning.h>
 #elif !defined(API_LEVEL_AT_LEAST)
 #if defined(__BIONIC__)
@@ -32,7 +32,7 @@
 #else
 #define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) (true)
 #endif  // __BIONIC__
-#endif  // __ANDROID_VENDOR__
+#endif  // __ANDROID_VENDOR_API__
 
 namespace aidl::android::os {
 
diff --git a/libs/binder/tests/binderCacheUnitTest.cpp b/libs/binder/tests/binderCacheUnitTest.cpp
index be5d559..c5ad793 100644
--- a/libs/binder/tests/binderCacheUnitTest.cpp
+++ b/libs/binder/tests/binderCacheUnitTest.cpp
@@ -149,7 +149,7 @@
     EXPECT_EQ(OK, mServiceManager->addService(kCachedServiceName, binder2));
 
     // Confirm that new service is returned instead of old.
-    int retry_count = 5;
+    int retry_count = 20;
     sp<IBinder> result2;
     do {
         std::this_thread::sleep_for(std::chrono::milliseconds(50));
diff --git a/libs/binder/trusty/rust/binder_rpc_test/main.rs b/libs/binder/trusty/rust/binder_rpc_test/main.rs
index baea5a8..da1a86f 100644
--- a/libs/binder/trusty/rust/binder_rpc_test/main.rs
+++ b/libs/binder/trusty/rust/binder_rpc_test/main.rs
@@ -19,7 +19,7 @@
 use binder_rpc_test_aidl::aidl::IBinderRpcSession::{BnBinderRpcSession, IBinderRpcSession};
 use binder_rpc_test_aidl::aidl::IBinderRpcTest::{BnBinderRpcTest, IBinderRpcTest};
 use binder_rpc_test_session::MyBinderRpcSession;
-use libc::{clock_gettime, CLOCK_REALTIME};
+use libc::{clock_gettime, CLOCK_BOOTTIME};
 use rpcbinder::RpcSession;
 use trusty_std::ffi::{CString, FallibleCString};
 
@@ -56,7 +56,7 @@
     let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
 
     // Safety: Passing valid pointer to variable ts which lives past end of call
-    assert_eq!(unsafe { clock_gettime(CLOCK_REALTIME, &mut ts) }, 0);
+    assert_eq!(unsafe { clock_gettime(CLOCK_BOOTTIME, &mut ts) }, 0);
 
     ts.tv_sec as u64 * 1_000_000_000u64 + ts.tv_nsec as u64
 }
diff --git a/libs/binder/trusty/rust/rpcbinder/rules.mk b/libs/binder/trusty/rust/rpcbinder/rules.mk
index 97f5c03..04c63f7 100644
--- a/libs/binder/trusty/rust/rpcbinder/rules.mk
+++ b/libs/binder/trusty/rust/rpcbinder/rules.mk
@@ -29,8 +29,8 @@
 	$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
 	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
 	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_server_bindgen \
-	external/rust/crates/cfg-if \
-	external/rust/crates/foreign-types \
+	$(call FIND_CRATE,cfg-if) \
+	$(call FIND_CRATE,foreign-types) \
 	trusty/user/base/lib/tipc/rust \
 	trusty/user/base/lib/trusty-sys \
 
diff --git a/libs/binder/trusty/rust/rules.mk b/libs/binder/trusty/rust/rules.mk
index 36bd3a2..e622b22 100644
--- a/libs/binder/trusty/rust/rules.mk
+++ b/libs/binder/trusty/rust/rules.mk
@@ -27,8 +27,8 @@
 	$(LIBBINDER_DIR)/trusty/ndk \
 	$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
 	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
-	external/rust/crates/downcast-rs \
-	external/rust/crates/libc \
+	$(call FIND_CRATE,downcast-rs) \
+	$(call FIND_CRATE,libc) \
 	trusty/user/base/lib/trusty-sys \
 
 MODULE_RUSTFLAGS += \
diff --git a/libs/ftl/non_null_test.cpp b/libs/ftl/non_null_test.cpp
index 367b398..457237c 100644
--- a/libs/ftl/non_null_test.cpp
+++ b/libs/ftl/non_null_test.cpp
@@ -81,6 +81,31 @@
 static_assert(std::is_same_v<decltype(ftl::as_non_null(std::declval<const int* const>())),
                              ftl::NonNull<const int*>>);
 
+class Base {};
+class Derived : public Base {};
+
+static_assert(std::is_constructible_v<ftl::NonNull<void*>, ftl::NonNull<int*>>);
+static_assert(!std::is_constructible_v<ftl::NonNull<int*>, ftl::NonNull<void*>>);
+static_assert(std::is_constructible_v<ftl::NonNull<const int*>, ftl::NonNull<int*>>);
+static_assert(!std::is_constructible_v<ftl::NonNull<int*>, ftl::NonNull<const int*>>);
+static_assert(std::is_constructible_v<ftl::NonNull<Base*>, ftl::NonNull<Derived*>>);
+static_assert(!std::is_constructible_v<ftl::NonNull<Derived*>, ftl::NonNull<Base*>>);
+static_assert(std::is_constructible_v<ftl::NonNull<std::unique_ptr<const int>>,
+                                      ftl::NonNull<std::unique_ptr<int>>>);
+static_assert(std::is_constructible_v<ftl::NonNull<std::unique_ptr<Base>>,
+                                      ftl::NonNull<std::unique_ptr<Derived>>>);
+
+static_assert(std::is_assignable_v<ftl::NonNull<void*>, ftl::NonNull<int*>>);
+static_assert(!std::is_assignable_v<ftl::NonNull<int*>, ftl::NonNull<void*>>);
+static_assert(std::is_assignable_v<ftl::NonNull<const int*>, ftl::NonNull<int*>>);
+static_assert(!std::is_assignable_v<ftl::NonNull<int*>, ftl::NonNull<const int*>>);
+static_assert(std::is_assignable_v<ftl::NonNull<Base*>, ftl::NonNull<Derived*>>);
+static_assert(!std::is_assignable_v<ftl::NonNull<Derived*>, ftl::NonNull<Base*>>);
+static_assert(std::is_assignable_v<ftl::NonNull<std::unique_ptr<const int>>,
+                                   ftl::NonNull<std::unique_ptr<int>>>);
+static_assert(std::is_assignable_v<ftl::NonNull<std::unique_ptr<Base>>,
+                                   ftl::NonNull<std::unique_ptr<Derived>>>);
+
 }  // namespace
 
 TEST(NonNull, SwapRawPtr) {
@@ -156,4 +181,14 @@
   EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
 }
 
+TEST(NonNull, ImplicitConversion) {
+  int i = 123;
+  int j = 345;
+  auto ip = ftl::as_non_null(&i);
+  ftl::NonNull<void*> vp{ip};
+  EXPECT_EQ(vp.get(), &i);
+  vp = ftl::as_non_null(&j);
+  EXPECT_EQ(vp.get(), &j);
+}
+
 }  // namespace android::test
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index b109969..422c57b 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -69,7 +69,7 @@
         color(0),
         bufferTransform(0),
         transformToDisplayInverse(false),
-        crop(Rect::INVALID_RECT),
+        crop({0, 0, -1, -1}),
         dataspace(ui::Dataspace::UNKNOWN),
         surfaceDamageRegion(),
         api(-1),
@@ -109,7 +109,10 @@
     SAFE_PARCEL(output.writeUint32, flags);
     SAFE_PARCEL(output.writeUint32, mask);
     SAFE_PARCEL(matrix.write, output);
-    SAFE_PARCEL(output.write, crop);
+    SAFE_PARCEL(output.writeFloat, crop.top);
+    SAFE_PARCEL(output.writeFloat, crop.left);
+    SAFE_PARCEL(output.writeFloat, crop.bottom);
+    SAFE_PARCEL(output.writeFloat, crop.right);
     SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, relativeLayerSurfaceControl);
     SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, parentSurfaceControlForChild);
     SAFE_PARCEL(output.writeFloat, color.r);
@@ -218,7 +221,10 @@
     SAFE_PARCEL(input.readUint32, &mask);
 
     SAFE_PARCEL(matrix.read, input);
-    SAFE_PARCEL(input.read, crop);
+    SAFE_PARCEL(input.readFloat, &crop.top);
+    SAFE_PARCEL(input.readFloat, &crop.left);
+    SAFE_PARCEL(input.readFloat, &crop.bottom);
+    SAFE_PARCEL(input.readFloat, &crop.right);
 
     SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &relativeLayerSurfaceControl);
     SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &parentSurfaceControlForChild);
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index df58df4..063aabb 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1539,14 +1539,7 @@
         mStatus = BAD_INDEX;
         return *this;
     }
-    if ((mask & layer_state_t::eLayerOpaque) || (mask & layer_state_t::eLayerHidden) ||
-        (mask & layer_state_t::eLayerSecure) || (mask & layer_state_t::eLayerSkipScreenshot) ||
-        (mask & layer_state_t::eEnableBackpressure) ||
-        (mask & layer_state_t::eIgnoreDestinationFrame) ||
-        (mask & layer_state_t::eLayerIsDisplayDecoration) ||
-        (mask & layer_state_t::eLayerIsRefreshRateIndicator)) {
-        s->what |= layer_state_t::eFlagsChanged;
-    }
+    s->what |= layer_state_t::eFlagsChanged;
     s->flags &= ~mask;
     s->flags |= (flags & mask);
     s->mask |= mask;
@@ -1652,6 +1645,11 @@
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCrop(
         const sp<SurfaceControl>& sc, const Rect& crop) {
+    return setCrop(sc, crop.toFloatRect());
+}
+
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCrop(
+        const sp<SurfaceControl>& sc, const FloatRect& crop) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
@@ -1941,6 +1939,20 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setLuts(
+        const sp<SurfaceControl>& sc, const base::unique_fd& /*lutFd*/,
+        const std::vector<int32_t>& /*offsets*/, const std::vector<int32_t>& /*dimensions*/,
+        const std::vector<int32_t>& /*sizes*/, const std::vector<int32_t>& /*samplingKeys*/) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    // TODO (b/329472856): update layer_state_t for lut(s)
+    registerSurfaceControlForCallback(sc);
+    return *this;
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCachingHint(
         const sp<SurfaceControl>& sc, gui::CachingHint cachingHint) {
     layer_state_t* s = getLayerState(sc);
diff --git a/libs/gui/aidl/android/gui/Lut.aidl b/libs/gui/aidl/android/gui/Lut.aidl
new file mode 100644
index 0000000..a06e521
--- /dev/null
+++ b/libs/gui/aidl/android/gui/Lut.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+import android.gui.LutProperties;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * This mirrors aidl::android::hardware::graphics::composer3::Lut definition
+ * @hide
+ */
+parcelable Lut {
+    @nullable ParcelFileDescriptor pfd;
+
+    LutProperties lutProperties;
+}
\ No newline at end of file
diff --git a/libs/gui/aidl/android/gui/LutProperties.aidl b/libs/gui/aidl/android/gui/LutProperties.aidl
new file mode 100644
index 0000000..561e0c0
--- /dev/null
+++ b/libs/gui/aidl/android/gui/LutProperties.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+/**
+ * This mirrors aidl::android::hardware::graphics::composer3::LutProperties definition.
+ * @hide
+ */
+parcelable LutProperties {
+    @Backing(type="int")
+    enum Dimension { ONE_D = 1, THREE_D = 3 }
+    Dimension dimension;
+
+    long size;
+    @Backing(type="int")
+    enum SamplingKey { RGB, MAX_RGB }
+    SamplingKey[] samplingKeys;
+}
\ No newline at end of file
diff --git a/libs/gui/aidl/android/gui/OverlayProperties.aidl b/libs/gui/aidl/android/gui/OverlayProperties.aidl
index 5fb1a83..7f41bda 100644
--- a/libs/gui/aidl/android/gui/OverlayProperties.aidl
+++ b/libs/gui/aidl/android/gui/OverlayProperties.aidl
@@ -16,6 +16,8 @@
 
 package android.gui;
 
+import android.gui.LutProperties;
+
 /** @hide */
 parcelable OverlayProperties {
     parcelable SupportedBufferCombinations {
@@ -27,4 +29,6 @@
     SupportedBufferCombinations[] combinations;
 
     boolean supportMixedColorSpaces;
+
+    @nullable LutProperties[] lutProperties;
 }
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 2cdde32..00065c8 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -330,7 +330,7 @@
     Region transparentRegion;
     uint32_t bufferTransform;
     bool transformToDisplayInverse;
-    Rect crop;
+    FloatRect crop;
     std::shared_ptr<BufferData> bufferData = nullptr;
     ui::Dataspace dataspace;
     HdrMetadata hdrMetadata;
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 4f9af16..5ea0c16 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -549,6 +549,7 @@
         Transaction& setMatrix(const sp<SurfaceControl>& sc,
                 float dsdx, float dtdx, float dtdy, float dsdy);
         Transaction& setCrop(const sp<SurfaceControl>& sc, const Rect& crop);
+        Transaction& setCrop(const sp<SurfaceControl>& sc, const FloatRect& crop);
         Transaction& setCornerRadius(const sp<SurfaceControl>& sc, float cornerRadius);
         Transaction& setBackgroundBlurRadius(const sp<SurfaceControl>& sc,
                                              int backgroundBlurRadius);
@@ -601,6 +602,11 @@
         Transaction& setExtendedRangeBrightness(const sp<SurfaceControl>& sc,
                                                 float currentBufferRatio, float desiredRatio);
         Transaction& setDesiredHdrHeadroom(const sp<SurfaceControl>& sc, float desiredRatio);
+        Transaction& setLuts(const sp<SurfaceControl>& sc, const base::unique_fd& lutFd,
+                             const std::vector<int32_t>& offsets,
+                             const std::vector<int32_t>& dimensions,
+                             const std::vector<int32_t>& sizes,
+                             const std::vector<int32_t>& samplingKeys);
         Transaction& setCachingHint(const sp<SurfaceControl>& sc, gui::CachingHint cachingHint);
         Transaction& setHdrMetadata(const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata);
         Transaction& setSurfaceDamageRegion(const sp<SurfaceControl>& sc,
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index d3f2899..1c7e0e4 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -109,6 +109,14 @@
 } # wb_libcameraservice
 
 flag {
+  name: "wb_unlimited_slots"
+  namespace: "core_graphics"
+  description: "Adds APIs and updates the implementation of bufferqueues to have a user-defined slot count."
+  bug: "341359814"
+  is_fixed_read_only: true
+} # wb_unlimited_slots
+
+flag {
   name: "bq_producer_throttles_only_async_mode"
   namespace: "core_graphics"
   description: "BufferQueueProducer only CPU throttle on queueBuffer() in async mode."
diff --git a/libs/gui/tests/SamplingDemo.cpp b/libs/gui/tests/SamplingDemo.cpp
index f98437b..8fea689 100644
--- a/libs/gui/tests/SamplingDemo.cpp
+++ b/libs/gui/tests/SamplingDemo.cpp
@@ -46,7 +46,8 @@
 
         SurfaceComposerClient::Transaction{}
                 .setLayer(mButton, 0x7fffffff)
-                .setCrop(mButton, {0, 0, width - 2 * BUTTON_PADDING, height - 2 * BUTTON_PADDING})
+                .setCrop(mButton,
+                         Rect{0, 0, width - 2 * BUTTON_PADDING, height - 2 * BUTTON_PADDING})
                 .setPosition(mButton, samplingArea.left + BUTTON_PADDING,
                              samplingArea.top + BUTTON_PADDING)
                 .setColor(mButton, half3{1, 1, 1})
@@ -59,7 +60,8 @@
         SurfaceComposerClient::Transaction{}
                 .setLayer(mButtonBlend, 0x7ffffffe)
                 .setCrop(mButtonBlend,
-                         {0, 0, width - 2 * SAMPLE_AREA_PADDING, height - 2 * SAMPLE_AREA_PADDING})
+                         Rect{0, 0, width - 2 * SAMPLE_AREA_PADDING,
+                              height - 2 * SAMPLE_AREA_PADDING})
                 .setPosition(mButtonBlend, samplingArea.left + SAMPLE_AREA_PADDING,
                              samplingArea.top + SAMPLE_AREA_PADDING)
                 .setColor(mButtonBlend, half3{1, 1, 1})
@@ -75,7 +77,7 @@
 
             SurfaceComposerClient::Transaction{}
                     .setLayer(mSamplingArea, 0x7ffffffd)
-                    .setCrop(mSamplingArea, {0, 0, 100, 32})
+                    .setCrop(mSamplingArea, Rect{0, 0, 100, 32})
                     .setPosition(mSamplingArea, 490, 1606)
                     .setColor(mSamplingArea, half3{0, 1, 0})
                     .setAlpha(mSamplingArea, 0.1)
diff --git a/libs/input/Resampler.cpp b/libs/input/Resampler.cpp
index 328fa68..1adff7b 100644
--- a/libs/input/Resampler.cpp
+++ b/libs/input/Resampler.cpp
@@ -249,6 +249,11 @@
                                           const InputMessage* futureSample) {
     const nanoseconds resampleTime = frameTime - RESAMPLE_LATENCY;
 
+    if (resampleTime.count() == motionEvent.getEventTime()) {
+        LOG_IF(INFO, debugResampling()) << "Not resampled. Resample time equals motion event time.";
+        return;
+    }
+
     updateLatestSamples(motionEvent);
 
     const std::optional<Sample> sample = (futureSample != nullptr)
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 81c6175..661c9f7 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -17,6 +17,7 @@
         "IdGenerator_test.cpp",
         "InputChannel_test.cpp",
         "InputConsumer_test.cpp",
+        "InputConsumerResampling_test.cpp",
         "InputDevice_test.cpp",
         "InputEvent_test.cpp",
         "InputPublisherAndConsumer_test.cpp",
diff --git a/libs/input/tests/InputConsumerResampling_test.cpp b/libs/input/tests/InputConsumerResampling_test.cpp
new file mode 100644
index 0000000..b139e87
--- /dev/null
+++ b/libs/input/tests/InputConsumerResampling_test.cpp
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <input/InputConsumerNoResampling.h>
+
+#include <chrono>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <TestEventMatchers.h>
+#include <TestInputChannel.h>
+#include <attestation/HmacKeyManager.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/BlockingQueue.h>
+#include <input/InputEventBuilders.h>
+#include <input/Resampler.h>
+#include <utils/Looper.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace {
+
+using std::chrono::nanoseconds;
+using namespace std::chrono_literals;
+
+struct Pointer {
+    int32_t id{0};
+    float x{0.0f};
+    float y{0.0f};
+    ToolType toolType{ToolType::FINGER};
+    bool isResampled{false};
+
+    PointerBuilder asPointerBuilder() const {
+        return PointerBuilder{id, toolType}.x(x).y(y).isResampled(isResampled);
+    }
+};
+
+struct InputEventEntry {
+    std::chrono::nanoseconds eventTime{0};
+    std::vector<Pointer> pointers{};
+    int32_t action{-1};
+};
+
+} // namespace
+
+class InputConsumerResamplingTest : public ::testing::Test, public InputConsumerCallbacks {
+protected:
+    InputConsumerResamplingTest()
+          : mClientTestChannel{std::make_shared<TestInputChannel>("TestChannel")},
+            mLooper{sp<Looper>::make(/*allowNonCallbacks=*/false)} {
+        Looper::setForThread(mLooper);
+        mConsumer = std::make_unique<
+                InputConsumerNoResampling>(mClientTestChannel, mLooper, *this,
+                                           []() { return std::make_unique<LegacyResampler>(); });
+    }
+
+    void invokeLooperCallback() const {
+        sp<LooperCallback> callback;
+        ASSERT_TRUE(mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr,
+                                             /*events=*/nullptr, &callback, /*data=*/nullptr));
+        ASSERT_NE(callback, nullptr);
+        callback->handleEvent(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT, /*data=*/nullptr);
+    }
+
+    InputMessage nextPointerMessage(const InputEventEntry& entry);
+
+    void assertReceivedMotionEvent(const std::vector<InputEventEntry>& expectedEntries);
+
+    std::shared_ptr<TestInputChannel> mClientTestChannel;
+    sp<Looper> mLooper;
+    std::unique_ptr<InputConsumerNoResampling> mConsumer;
+
+    BlockingQueue<std::unique_ptr<KeyEvent>> mKeyEvents;
+    BlockingQueue<std::unique_ptr<MotionEvent>> mMotionEvents;
+    BlockingQueue<std::unique_ptr<FocusEvent>> mFocusEvents;
+    BlockingQueue<std::unique_ptr<CaptureEvent>> mCaptureEvents;
+    BlockingQueue<std::unique_ptr<DragEvent>> mDragEvents;
+    BlockingQueue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
+
+private:
+    uint32_t mLastSeq{0};
+    size_t mOnBatchedInputEventPendingInvocationCount{0};
+
+    // InputConsumerCallbacks interface
+    void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override {
+        mKeyEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override {
+        mMotionEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onBatchedInputEventPending(int32_t pendingBatchSource) override {
+        if (!mConsumer->probablyHasInput()) {
+            ADD_FAILURE() << "should deterministically have input because there is a batch";
+        }
+        ++mOnBatchedInputEventPendingInvocationCount;
+    }
+    void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
+        mFocusEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override {
+        mCaptureEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override {
+        mDragEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override {
+        mTouchModeEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+};
+
+InputMessage InputConsumerResamplingTest::nextPointerMessage(const InputEventEntry& entry) {
+    ++mLastSeq;
+    InputMessageBuilder messageBuilder = InputMessageBuilder{InputMessage::Type::MOTION, mLastSeq}
+                                                 .eventTime(entry.eventTime.count())
+                                                 .deviceId(1)
+                                                 .action(entry.action)
+                                                 .downTime(0);
+    for (const Pointer& pointer : entry.pointers) {
+        messageBuilder.pointer(pointer.asPointerBuilder());
+    }
+    return messageBuilder.build();
+}
+
+void InputConsumerResamplingTest::assertReceivedMotionEvent(
+        const std::vector<InputEventEntry>& expectedEntries) {
+    std::unique_ptr<MotionEvent> motionEvent = mMotionEvents.pop();
+    ASSERT_NE(motionEvent, nullptr);
+
+    ASSERT_EQ(motionEvent->getHistorySize() + 1, expectedEntries.size());
+
+    for (size_t sampleIndex = 0; sampleIndex < expectedEntries.size(); ++sampleIndex) {
+        SCOPED_TRACE("sampleIndex: " + std::to_string(sampleIndex));
+        const InputEventEntry& expectedEntry = expectedEntries[sampleIndex];
+        EXPECT_EQ(motionEvent->getHistoricalEventTime(sampleIndex),
+                  expectedEntry.eventTime.count());
+        EXPECT_EQ(motionEvent->getPointerCount(), expectedEntry.pointers.size());
+        EXPECT_EQ(motionEvent->getAction(), expectedEntry.action);
+
+        for (size_t pointerIndex = 0; pointerIndex < expectedEntry.pointers.size();
+             ++pointerIndex) {
+            SCOPED_TRACE("pointerIndex: " + std::to_string(pointerIndex));
+            ssize_t eventPointerIndex =
+                    motionEvent->findPointerIndex(expectedEntry.pointers[pointerIndex].id);
+            EXPECT_EQ(motionEvent->getHistoricalRawX(eventPointerIndex, sampleIndex),
+                      expectedEntry.pointers[pointerIndex].x);
+            EXPECT_EQ(motionEvent->getHistoricalRawY(eventPointerIndex, sampleIndex),
+                      expectedEntry.pointers[pointerIndex].y);
+            EXPECT_EQ(motionEvent->getHistoricalX(eventPointerIndex, sampleIndex),
+                      expectedEntry.pointers[pointerIndex].x);
+            EXPECT_EQ(motionEvent->getHistoricalY(eventPointerIndex, sampleIndex),
+                      expectedEntry.pointers[pointerIndex].y);
+            EXPECT_EQ(motionEvent->isResampled(pointerIndex, sampleIndex),
+                      expectedEntry.pointers[pointerIndex].isResampled);
+        }
+    }
+}
+
+/**
+ * Timeline
+ * ---------+------------------+------------------+--------+-----------------+----------------------
+ *          0 ms               10 ms              20 ms    25 ms            35 ms
+ *          ACTION_DOWN       ACTION_MOVE      ACTION_MOVE  ^                ^
+ *                                                          |                |
+ *                                                         resampled value   |
+ *                                                                          frameTime
+ * Typically, the prediction is made for time frameTime - RESAMPLE_LATENCY, or 30 ms in this case,
+ * where RESAMPLE_LATENCY equals 5 milliseconds. However, that would be 10 ms later than the last
+ * real sample (which came in at 20 ms). Therefore, the resampling should happen at 20 ms +
+ * RESAMPLE_MAX_PREDICTION = 28 ms, where RESAMPLE_MAX_PREDICTION equals 8 milliseconds. In this
+ * situation, though, resample time is further limited by taking half of the difference between the
+ * last two real events, which would put this time at: 20 ms + (20 ms - 10 ms) / 2 = 25 ms.
+ */
+TEST_F(InputConsumerResamplingTest, EventIsResampled) {
+    // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+    // InputEvent with a single action.
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+    mClientTestChannel->assertNoSentMessages();
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent({InputEventEntry{0ms,
+                                               {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
+                                               AMOTION_EVENT_ACTION_DOWN}});
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
+    assertReceivedMotionEvent(
+            {InputEventEntry{10ms,
+                             {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
+                             AMOTION_EVENT_ACTION_MOVE},
+             InputEventEntry{20ms,
+                             {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
+                             AMOTION_EVENT_ACTION_MOVE},
+             InputEventEntry{25ms,
+                             {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
+                             AMOTION_EVENT_ACTION_MOVE}});
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+/**
+ * Same as above test, but use pointer id=1 instead of 0 to make sure that system does not
+ * have these hardcoded.
+ */
+TEST_F(InputConsumerResamplingTest, EventIsResampledWithDifferentId) {
+    // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+    // InputEvent with a single action.
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {0ms, {Pointer{.id = 1, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+    mClientTestChannel->assertNoSentMessages();
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent({InputEventEntry{0ms,
+                                               {Pointer{.id = 1, .x = 10.0f, .y = 20.0f}},
+                                               AMOTION_EVENT_ACTION_DOWN}});
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {10ms, {Pointer{.id = 1, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {20ms, {Pointer{.id = 1, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
+    assertReceivedMotionEvent(
+            {InputEventEntry{10ms,
+                             {Pointer{.id = 1, .x = 20.0f, .y = 30.0f}},
+                             AMOTION_EVENT_ACTION_MOVE},
+             InputEventEntry{20ms,
+                             {Pointer{.id = 1, .x = 30.0f, .y = 30.0f}},
+                             AMOTION_EVENT_ACTION_MOVE},
+             InputEventEntry{25ms,
+                             {Pointer{.id = 1, .x = 35.0f, .y = 30.0f, .isResampled = true}},
+                             AMOTION_EVENT_ACTION_MOVE}});
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+/**
+ * Stylus pointer coordinates are resampled.
+ */
+TEST_F(InputConsumerResamplingTest, StylusEventIsResampled) {
+    // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+    // InputEvent with a single action.
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {0ms,
+             {Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::STYLUS}},
+             AMOTION_EVENT_ACTION_DOWN}));
+
+    mClientTestChannel->assertNoSentMessages();
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent({InputEventEntry{0ms,
+                                               {Pointer{.id = 0,
+                                                        .x = 10.0f,
+                                                        .y = 20.0f,
+                                                        .toolType = ToolType::STYLUS}},
+                                               AMOTION_EVENT_ACTION_DOWN}});
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {10ms,
+             {Pointer{.id = 0, .x = 20.0f, .y = 30.0f, .toolType = ToolType::STYLUS}},
+             AMOTION_EVENT_ACTION_MOVE}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {20ms,
+             {Pointer{.id = 0, .x = 30.0f, .y = 30.0f, .toolType = ToolType::STYLUS}},
+             AMOTION_EVENT_ACTION_MOVE}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
+    assertReceivedMotionEvent({InputEventEntry{10ms,
+                                               {Pointer{.id = 0,
+                                                        .x = 20.0f,
+                                                        .y = 30.0f,
+                                                        .toolType = ToolType::STYLUS}},
+                                               AMOTION_EVENT_ACTION_MOVE},
+                               InputEventEntry{20ms,
+                                               {Pointer{.id = 0,
+                                                        .x = 30.0f,
+                                                        .y = 30.0f,
+                                                        .toolType = ToolType::STYLUS}},
+                                               AMOTION_EVENT_ACTION_MOVE},
+                               InputEventEntry{25ms,
+                                               {Pointer{.id = 0,
+                                                        .x = 35.0f,
+                                                        .y = 30.0f,
+                                                        .toolType = ToolType::STYLUS,
+                                                        .isResampled = true}},
+                                               AMOTION_EVENT_ACTION_MOVE}});
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+/**
+ * Mouse pointer coordinates are resampled.
+ */
+TEST_F(InputConsumerResamplingTest, MouseEventIsResampled) {
+    // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+    // InputEvent with a single action.
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {0ms,
+             {Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::MOUSE}},
+             AMOTION_EVENT_ACTION_DOWN}));
+
+    mClientTestChannel->assertNoSentMessages();
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent({InputEventEntry{0ms,
+                                               {Pointer{.id = 0,
+                                                        .x = 10.0f,
+                                                        .y = 20.0f,
+                                                        .toolType = ToolType::MOUSE}},
+                                               AMOTION_EVENT_ACTION_DOWN}});
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {10ms,
+             {Pointer{.id = 0, .x = 20.0f, .y = 30.0f, .toolType = ToolType::MOUSE}},
+             AMOTION_EVENT_ACTION_MOVE}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {20ms,
+             {Pointer{.id = 0, .x = 30.0f, .y = 30.0f, .toolType = ToolType::MOUSE}},
+             AMOTION_EVENT_ACTION_MOVE}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
+    assertReceivedMotionEvent({InputEventEntry{10ms,
+                                               {Pointer{.id = 0,
+                                                        .x = 20.0f,
+                                                        .y = 30.0f,
+                                                        .toolType = ToolType::MOUSE}},
+                                               AMOTION_EVENT_ACTION_MOVE},
+                               InputEventEntry{20ms,
+                                               {Pointer{.id = 0,
+                                                        .x = 30.0f,
+                                                        .y = 30.0f,
+                                                        .toolType = ToolType::MOUSE}},
+                                               AMOTION_EVENT_ACTION_MOVE},
+                               InputEventEntry{25ms,
+                                               {Pointer{.id = 0,
+                                                        .x = 35.0f,
+                                                        .y = 30.0f,
+                                                        .toolType = ToolType::MOUSE,
+                                                        .isResampled = true}},
+                                               AMOTION_EVENT_ACTION_MOVE}});
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+/**
+ * Motion events with palm tool type are not resampled.
+ */
+TEST_F(InputConsumerResamplingTest, PalmEventIsNotResampled) {
+    // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+    // InputEvent with a single action.
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {0ms,
+             {Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::PALM}},
+             AMOTION_EVENT_ACTION_DOWN}));
+
+    mClientTestChannel->assertNoSentMessages();
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent(
+            {InputEventEntry{0ms,
+                             {Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::PALM}},
+                             AMOTION_EVENT_ACTION_DOWN}});
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {10ms,
+             {Pointer{.id = 0, .x = 20.0f, .y = 30.0f, .toolType = ToolType::PALM}},
+             AMOTION_EVENT_ACTION_MOVE}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {20ms,
+             {Pointer{.id = 0, .x = 30.0f, .y = 30.0f, .toolType = ToolType::PALM}},
+             AMOTION_EVENT_ACTION_MOVE}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
+    assertReceivedMotionEvent(
+            {InputEventEntry{10ms,
+                             {Pointer{.id = 0, .x = 20.0f, .y = 30.0f, .toolType = ToolType::PALM}},
+                             AMOTION_EVENT_ACTION_MOVE},
+             InputEventEntry{20ms,
+                             {Pointer{.id = 0, .x = 30.0f, .y = 30.0f, .toolType = ToolType::PALM}},
+                             AMOTION_EVENT_ACTION_MOVE}});
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+/**
+ * Event should not be resampled when sample time is equal to event time.
+ */
+TEST_F(InputConsumerResamplingTest, SampleTimeEqualsEventTime) {
+    // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+    // InputEvent with a single action.
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+    mClientTestChannel->assertNoSentMessages();
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent({InputEventEntry{0ms,
+                                               {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
+                                               AMOTION_EVENT_ACTION_DOWN}});
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(nanoseconds{20ms + 5ms /*RESAMPLE_LATENCY*/}.count());
+
+    // MotionEvent should not resampled because the resample time falls exactly on the existing
+    // event time.
+    assertReceivedMotionEvent({InputEventEntry{10ms,
+                                               {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
+                                               AMOTION_EVENT_ACTION_MOVE},
+                               InputEventEntry{20ms,
+                                               {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
+                                               AMOTION_EVENT_ACTION_MOVE}});
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+} // namespace android
diff --git a/libs/nativewindow/rust/Android.bp b/libs/nativewindow/rust/Android.bp
index d68d6ba..c572ee7 100644
--- a/libs/nativewindow/rust/Android.bp
+++ b/libs/nativewindow/rust/Android.bp
@@ -26,6 +26,7 @@
     source_stem: "bindings",
     bindgen_flags: [
         "--constified-enum-module=AHardwareBuffer_Format",
+        "--bitfield-enum=ADataSpace",
         "--bitfield-enum=AHardwareBuffer_UsageFlags",
 
         "--allowlist-file=.*/nativewindow/include/.*\\.h",
@@ -110,6 +111,7 @@
     srcs: ["src/lib.rs"],
     rustlibs: [
         "libbinder_rs",
+        "libbitflags",
         "libnativewindow_bindgen",
     ],
 }
diff --git a/libs/nativewindow/rust/src/surface.rs b/libs/nativewindow/rust/src/surface.rs
index 25fea80..9eddfcd 100644
--- a/libs/nativewindow/rust/src/surface.rs
+++ b/libs/nativewindow/rust/src/surface.rs
@@ -20,10 +20,14 @@
     unstable_api::{status_result, AsNative},
     StatusCode,
 };
+use bitflags::bitflags;
 use nativewindow_bindgen::{
-    AHardwareBuffer_Format, ANativeWindow, ANativeWindow_acquire, ANativeWindow_getFormat,
-    ANativeWindow_getHeight, ANativeWindow_getWidth, ANativeWindow_readFromParcel,
-    ANativeWindow_release, ANativeWindow_writeToParcel,
+    ADataSpace, AHardwareBuffer_Format, ANativeWindow, ANativeWindow_acquire,
+    ANativeWindow_getBuffersDataSpace, ANativeWindow_getBuffersDefaultDataSpace,
+    ANativeWindow_getFormat, ANativeWindow_getHeight, ANativeWindow_getWidth,
+    ANativeWindow_readFromParcel, ANativeWindow_release, ANativeWindow_setBuffersDataSpace,
+    ANativeWindow_setBuffersGeometry, ANativeWindow_setBuffersTransform,
+    ANativeWindow_writeToParcel,
 };
 use std::error::Error;
 use std::fmt::{self, Debug, Display, Formatter};
@@ -60,6 +64,95 @@
         let format = unsafe { ANativeWindow_getFormat(self.0.as_ptr()) };
         format.try_into().map_err(|_| ErrorCode(format))
     }
+
+    /// Changes the format and size of the window buffers.
+    ///
+    /// The width and height control the number of pixels in the buffers, not the dimensions of the
+    /// window on screen. If these are different than the window's physical size, then its buffer
+    /// will be scaled to match that size when compositing it to the screen. The width and height
+    /// must be either both zero or both non-zero. If both are 0 then the window's base value will
+    /// come back in force.
+    pub fn set_buffers_geometry(
+        &mut self,
+        width: i32,
+        height: i32,
+        format: AHardwareBuffer_Format::Type,
+    ) -> Result<(), ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let status = unsafe {
+            ANativeWindow_setBuffersGeometry(
+                self.0.as_ptr(),
+                width,
+                height,
+                format.try_into().expect("Invalid format"),
+            )
+        };
+
+        if status == 0 {
+            Ok(())
+        } else {
+            Err(ErrorCode(status))
+        }
+    }
+
+    /// Sets a transfom that will be applied to future buffers posted to the window.
+    pub fn set_buffers_transform(&mut self, transform: Transform) -> Result<(), ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let status =
+            unsafe { ANativeWindow_setBuffersTransform(self.0.as_ptr(), transform.bits() as i32) };
+
+        if status == 0 {
+            Ok(())
+        } else {
+            Err(ErrorCode(status))
+        }
+    }
+
+    /// Sets the data space that will be applied to future buffers posted to the window.
+    pub fn set_buffers_data_space(&mut self, data_space: ADataSpace) -> Result<(), ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let status = unsafe { ANativeWindow_setBuffersDataSpace(self.0.as_ptr(), data_space.0) };
+
+        if status == 0 {
+            Ok(())
+        } else {
+            Err(ErrorCode(status))
+        }
+    }
+
+    /// Gets the data space of the buffers in the window.
+    pub fn get_buffers_data_space(&mut self) -> Result<ADataSpace, ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let data_space = unsafe { ANativeWindow_getBuffersDataSpace(self.0.as_ptr()) };
+
+        if data_space < 0 {
+            Err(ErrorCode(data_space))
+        } else {
+            Ok(ADataSpace(data_space))
+        }
+    }
+
+    /// Gets the default data space of the buffers in the window as set by the consumer.
+    pub fn get_buffers_default_data_space(&mut self) -> Result<ADataSpace, ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let data_space = unsafe { ANativeWindow_getBuffersDefaultDataSpace(self.0.as_ptr()) };
+
+        if data_space < 0 {
+            Err(ErrorCode(data_space))
+        } else {
+            Ok(ADataSpace(data_space))
+        }
+    }
 }
 
 impl Drop for Surface {
@@ -141,3 +234,19 @@
         write!(f, "Error {}", self.0)
     }
 }
+
+bitflags! {
+    /// Transforms that can be applied to buffers as they are displayed to a window.
+    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+    pub struct Transform: u32 {
+        const MIRROR_HORIZONTAL = 0x01;
+        const MIRROR_VERTICAL = 0x02;
+        const ROTATE_90 = 0x04;
+    }
+}
+
+impl Transform {
+    pub const IDENTITY: Self = Self::empty();
+    pub const ROTATE_180: Self = Self::MIRROR_HORIZONTAL.union(Self::MIRROR_VERTICAL);
+    pub const ROTATE_270: Self = Self::ROTATE_180.union(Self::ROTATE_90);
+}
diff --git a/libs/ui/include/ui/FloatRect.h b/libs/ui/include/ui/FloatRect.h
index 4c9c7b7..4366db5 100644
--- a/libs/ui/include/ui/FloatRect.h
+++ b/libs/ui/include/ui/FloatRect.h
@@ -51,6 +51,9 @@
     float bottom = 0.0f;
 
     constexpr bool isEmpty() const { return !(left < right && top < bottom); }
+
+    // a valid rectangle has a non negative width and height
+    inline bool isValid() const { return (getWidth() >= 0) && (getHeight() >= 0); }
 };
 
 inline bool operator==(const FloatRect& a, const FloatRect& b) {
diff --git a/libs/ui/include/ui/Rect.h b/libs/ui/include/ui/Rect.h
index 2eb9330..2307b44 100644
--- a/libs/ui/include/ui/Rect.h
+++ b/libs/ui/include/ui/Rect.h
@@ -74,12 +74,10 @@
     }
 
     inline explicit Rect(const FloatRect& floatRect) {
-        // Ideally we would use std::round, but we don't want to add an STL
-        // dependency here, so we use an approximation
-        left = static_cast<int32_t>(floatRect.left + 0.5f);
-        top = static_cast<int32_t>(floatRect.top + 0.5f);
-        right = static_cast<int32_t>(floatRect.right + 0.5f);
-        bottom = static_cast<int32_t>(floatRect.bottom + 0.5f);
+        left = static_cast<int32_t>(std::round(floatRect.left));
+        top = static_cast<int32_t>(std::round(floatRect.top));
+        right = static_cast<int32_t>(std::round(floatRect.right));
+        bottom = static_cast<int32_t>(std::round(floatRect.bottom));
     }
 
     inline explicit Rect(const ui::Size& size) {
diff --git a/libs/ui/tests/Rect_test.cpp b/libs/ui/tests/Rect_test.cpp
index 9cc36bb..c3c8bd9 100644
--- a/libs/ui/tests/Rect_test.cpp
+++ b/libs/ui/tests/Rect_test.cpp
@@ -99,6 +99,16 @@
         EXPECT_EQ(30, rect.right);
         EXPECT_EQ(40, rect.bottom);
     }
+
+    EXPECT_EQ(Rect(0, 1, -1, 0), Rect(FloatRect(0.f, 1.f, -1.f, 0.f)));
+    EXPECT_EQ(Rect(100000, 100000, -100000, -100000),
+              Rect(FloatRect(100000.f, 100000.f, -100000.f, -100000.f)));
+
+    // round down if < .5
+    EXPECT_EQ(Rect(0, 1, -1, 0), Rect(FloatRect(0.4f, 1.1f, -1.499f, 0.1f)));
+
+    // round up if >= .5
+    EXPECT_EQ(Rect(20, 20, -20, -20), Rect(FloatRect(19.5f, 19.9f, -19.5f, -19.9f)));
 }
 
 TEST(RectTest, makeInvalid) {
diff --git a/libs/ultrahdr/Android.bp b/libs/ultrahdr/Android.bp
deleted file mode 100644
index eda5ea4..0000000
--- a/libs/ultrahdr/Android.bp
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2022 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    // See: http://go/android-license-faq
-    default_applicable_licenses: [
-        "frameworks_native_license",
-        "adobe_hdr_gain_map_license",
-    ],
-}
-
-cc_library {
-    name: "libultrahdr-deprecated",
-    enabled: false,
-    host_supported: true,
-    vendor_available: true,
-    export_include_dirs: ["include"],
-    local_include_dirs: ["include"],
-
-    srcs: [
-        "icc.cpp",
-        "jpegr.cpp",
-        "gainmapmath.cpp",
-        "jpegrutils.cpp",
-        "multipictureformat.cpp",
-    ],
-
-    shared_libs: [
-        "libimage_io",
-        "libjpeg",
-        "libjpegencoder",
-        "libjpegdecoder",
-        "liblog",
-        "libutils",
-    ],
-}
-
-cc_library {
-    name: "libjpegencoder-deprecated",
-    enabled: false,
-    host_supported: true,
-    vendor_available: true,
-
-    shared_libs: [
-        "libjpeg",
-        "liblog",
-        "libutils",
-    ],
-
-    export_include_dirs: ["include"],
-
-    srcs: [
-        "jpegencoderhelper.cpp",
-    ],
-}
-
-cc_library {
-    name: "libjpegdecoder-deprecated",
-    enabled: false,
-    host_supported: true,
-    vendor_available: true,
-
-    shared_libs: [
-        "libjpeg",
-        "liblog",
-        "libutils",
-    ],
-
-    export_include_dirs: ["include"],
-
-    srcs: [
-        "jpegdecoderhelper.cpp",
-    ],
-}
diff --git a/libs/ultrahdr/fuzzer/Android.bp b/libs/ultrahdr/fuzzer/Android.bp
deleted file mode 100644
index 8d9132f..0000000
--- a/libs/ultrahdr/fuzzer/Android.bp
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_native_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_native_license"],
-}
-
-cc_defaults {
-    name: "ultrahdr_fuzzer_defaults-deprecated",
-    enabled: false,
-    host_supported: true,
-    shared_libs: [
-        "libimage_io",
-        "libjpeg",
-    ],
-    static_libs: [
-        "libjpegdecoder",
-        "libjpegencoder",
-        "libultrahdr",
-        "libutils",
-        "liblog",
-    ],
-    target: {
-        darwin: {
-            enabled: false,
-        },
-    },
-    fuzz_config: {
-        cc: [
-            "android-media-fuzzing-reports@google.com",
-        ],
-        description: "The fuzzers target the APIs of jpeg hdr",
-        service_privilege: "constrained",
-        users: "multi_user",
-        fuzzed_code_usage: "future_version",
-        vector: "local_no_privileges_required",
-    },
-}
-
-cc_fuzz {
-    name: "ultrahdr_enc_fuzzer-deprecated",
-    enabled: false,
-    defaults: ["ultrahdr_fuzzer_defaults"],
-    srcs: [
-        "ultrahdr_enc_fuzzer.cpp",
-    ],
-}
-
-cc_fuzz {
-    name: "ultrahdr_dec_fuzzer-deprecated",
-    enabled: false,
-    defaults: ["ultrahdr_fuzzer_defaults"],
-    srcs: [
-        "ultrahdr_dec_fuzzer.cpp",
-    ],
-}
diff --git a/libs/ultrahdr/tests/Android.bp b/libs/ultrahdr/tests/Android.bp
deleted file mode 100644
index 00cc797..0000000
--- a/libs/ultrahdr/tests/Android.bp
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2022 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_native_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_native_license"],
-}
-
-cc_test {
-    name: "ultrahdr_unit_test-deprecated",
-    enabled: false,
-    test_suites: ["device-tests"],
-    srcs: [
-        "gainmapmath_test.cpp",
-        "icchelper_test.cpp",
-        "jpegr_test.cpp",
-        "jpegencoderhelper_test.cpp",
-        "jpegdecoderhelper_test.cpp",
-    ],
-    shared_libs: [
-        "libimage_io",
-        "libjpeg",
-        "liblog",
-    ],
-    static_libs: [
-        "libgmock",
-        "libgtest",
-        "libjpegdecoder",
-        "libjpegencoder",
-        "libultrahdr",
-        "libutils",
-    ],
-    data: [
-        "./data/*.*",
-    ],
-}
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index bf0e38e..3be8ddc 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -599,6 +599,9 @@
     driver_t* hnd = nullptr;
 
     // ANGLE doesn't ship with GLES library, and thus we skip GLES driver.
+    // b/370113081: if there is no libEGL_angle.so in namespace ns, libEGL_angle.so in system
+    // partition will be loaded instead. If there is no libEGL_angle.so in system partition, no
+    // angle libs are loaded, and app that sets to use ANGLE will crash.
     void* dso = load_angle("EGL", ns);
     if (dso) {
         initialize_api(dso, cnx, EGL);
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index c2a9880..8161b47 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -173,7 +173,6 @@
         "DisplayHardware/VirtualDisplaySurface.cpp",
         "DisplayRenderArea.cpp",
         "Effects/Daltonizer.cpp",
-        "EventLog/EventLog.cpp",
         "FrontEnd/LayerCreationArgs.cpp",
         "FrontEnd/LayerHandle.cpp",
         "FrontEnd/LayerSnapshot.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index d1429a2..14a8fd6 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -155,7 +155,7 @@
     uint32_t geomBufferTransform{0};
     Rect geomBufferSize;
     Rect geomContentCrop;
-    Rect geomCrop;
+    FloatRect geomCrop;
 
     GenericLayerMetadataMap metadata;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index 4dbf8d2..dcfe21a 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -128,6 +128,10 @@
     // Applies a HWC device layer request
     virtual void applyDeviceLayerRequest(Hwc2::IComposerClient::LayerRequest request) = 0;
 
+    // Applies a HWC device layer lut
+    virtual void applyDeviceLayerLut(aidl::android::hardware::graphics::composer3::LutProperties,
+                                     ndk::ScopedFileDescriptor) = 0;
+
     // Returns true if the composition settings scale pixels
     virtual bool needsFiltering() const = 0;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index d1eff24..a39abb4 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -82,11 +82,13 @@
     using DisplayRequests = android::HWComposer::DeviceRequestedChanges::DisplayRequests;
     using LayerRequests = android::HWComposer::DeviceRequestedChanges::LayerRequests;
     using ClientTargetProperty = android::HWComposer::DeviceRequestedChanges::ClientTargetProperty;
+    using LayerLuts = android::HWComposer::DeviceRequestedChanges::LayerLuts;
     virtual bool allLayersRequireClientComposition() const;
     virtual void applyChangedTypesToLayers(const ChangedTypes&);
     virtual void applyDisplayRequests(const DisplayRequests&);
     virtual void applyLayerRequestsToLayers(const LayerRequests&);
     virtual void applyClientTargetRequests(const ClientTargetProperty&);
+    virtual void applyLayerLutsToLayers(const LayerLuts&);
 
     // Internal
     virtual void setConfiguration(const compositionengine::DisplayCreationArgs&);
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index f383392..354a441 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -60,6 +60,8 @@
             aidl::android::hardware::graphics::composer3::Composition) override;
     void prepareForDeviceLayerRequests() override;
     void applyDeviceLayerRequest(Hwc2::IComposerClient::LayerRequest request) override;
+    void applyDeviceLayerLut(aidl::android::hardware::graphics::composer3::LutProperties,
+                             ndk::ScopedFileDescriptor) override;
     bool needsFiltering() const override;
     std::optional<LayerFE::LayerSettings> getOverrideCompositionSettings() const override;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
index 5fef63a..48c2f9c 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
@@ -56,6 +56,9 @@
     MOCK_METHOD1(applyDeviceLayerRequest, void(Hwc2::IComposerClient::LayerRequest request));
     MOCK_CONST_METHOD0(needsFiltering, bool());
     MOCK_CONST_METHOD0(getOverrideCompositionSettings, std::optional<LayerFE::LayerSettings>());
+    MOCK_METHOD(void, applyDeviceLayerLut,
+                (aidl::android::hardware::graphics::composer3::LutProperties,
+                 ndk::ScopedFileDescriptor));
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 77b1940..b0164b7 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -278,6 +278,7 @@
         applyDisplayRequests(changes->displayRequests);
         applyLayerRequestsToLayers(changes->layerRequests);
         applyClientTargetRequests(changes->clientTargetProperty);
+        applyLayerLutsToLayers(changes->layerLuts);
     }
 
     // Determine what type of composition we are doing from the final state
@@ -359,6 +360,25 @@
             static_cast<ui::PixelFormat>(clientTargetProperty.clientTargetProperty.pixelFormat));
 }
 
+void Display::applyLayerLutsToLayers(const LayerLuts& layerLuts) {
+    auto& mapper = getCompositionEngine().getHwComposer().getLutFileDescriptorMapper();
+    for (auto* layer : getOutputLayersOrderedByZ()) {
+        auto hwcLayer = layer->getHwcLayer();
+        if (!hwcLayer) {
+            continue;
+        }
+
+        if (auto lutsIt = layerLuts.find(hwcLayer); lutsIt != layerLuts.end()) {
+            if (auto mapperIt = mapper.find(hwcLayer); mapperIt != mapper.end()) {
+                layer->applyDeviceLayerLut(lutsIt->second,
+                                           ndk::ScopedFileDescriptor(mapperIt->second.release()));
+            }
+        }
+    }
+
+    mapper.clear();
+}
+
 void Display::executeCommands() {
     const auto halDisplayIdOpt = HalDisplayId::tryCast(mId);
     if (mIsDisconnected || !halDisplayIdOpt) {
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 091c207..2d46dc0 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -24,6 +24,7 @@
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <cstdint>
 #include "system/graphics-base-v1.0.h"
+#include "ui/FloatRect.h"
 
 #include <ui/HdrRenderTypeUtils.h>
 
@@ -37,6 +38,7 @@
 #pragma clang diagnostic pop // ignored "-Wconversion"
 
 using aidl::android::hardware::graphics::composer3::Composition;
+using aidl::android::hardware::graphics::composer3::LutProperties;
 
 namespace android::compositionengine {
 
@@ -185,35 +187,35 @@
     const auto& layerState = *getLayerFE().getCompositionState();
     const auto& outputState = getOutput().getState();
 
+    // Convert from layer space to layerStackSpace
     // apply the layer's transform, followed by the display's global transform
     // here we're guaranteed that the layer's transform preserves rects
-    Region activeTransparentRegion = layerState.transparentRegionHint;
     const ui::Transform& layerTransform = layerState.geomLayerTransform;
-    const ui::Transform& inverseLayerTransform = layerState.geomInverseLayerTransform;
-    const Rect& bufferSize = layerState.geomBufferSize;
-    Rect activeCrop = layerState.geomCrop;
-    if (!activeCrop.isEmpty() && bufferSize.isValid()) {
-        activeCrop = layerTransform.transform(activeCrop);
-        if (!activeCrop.intersect(outputState.layerStackSpace.getContent(), &activeCrop)) {
-            activeCrop.clear();
-        }
-        activeCrop = inverseLayerTransform.transform(activeCrop, true);
-        // This needs to be here as transform.transform(Rect) computes the
-        // transformed rect and then takes the bounding box of the result before
-        // returning. This means
-        // transform.inverse().transform(transform.transform(Rect)) != Rect
-        // in which case we need to make sure the final rect is clipped to the
-        // display bounds.
-        if (!activeCrop.intersect(bufferSize, &activeCrop)) {
-            activeCrop.clear();
-        }
+    Region activeTransparentRegion = layerTransform.transform(layerState.transparentRegionHint);
+    if (!layerState.geomCrop.isEmpty() && layerState.geomBufferSize.isValid()) {
+        FloatRect activeCrop = layerTransform.transform(layerState.geomCrop);
+        activeCrop = activeCrop.intersect(outputState.layerStackSpace.getContent().toFloatRect());
+        const FloatRect& bufferSize =
+                layerTransform.transform(layerState.geomBufferSize.toFloatRect());
+        activeCrop = activeCrop.intersect(bufferSize);
+
         // mark regions outside the crop as transparent
-        activeTransparentRegion.orSelf(Rect(0, 0, bufferSize.getWidth(), activeCrop.top));
-        activeTransparentRegion.orSelf(
-                Rect(0, activeCrop.bottom, bufferSize.getWidth(), bufferSize.getHeight()));
-        activeTransparentRegion.orSelf(Rect(0, activeCrop.top, activeCrop.left, activeCrop.bottom));
-        activeTransparentRegion.orSelf(
-                Rect(activeCrop.right, activeCrop.top, bufferSize.getWidth(), activeCrop.bottom));
+        Rect topRegion = Rect(layerTransform.transform(
+                FloatRect(0, 0, layerState.geomBufferSize.getWidth(), layerState.geomCrop.top)));
+        Rect bottomRegion = Rect(layerTransform.transform(
+                FloatRect(0, layerState.geomCrop.bottom, layerState.geomBufferSize.getWidth(),
+                          layerState.geomBufferSize.getHeight())));
+        Rect leftRegion = Rect(layerTransform.transform(FloatRect(0, layerState.geomCrop.top,
+                                                                 layerState.geomCrop.left,
+                                                                 layerState.geomCrop.bottom)));
+        Rect rightRegion = Rect(layerTransform.transform(
+                FloatRect(layerState.geomCrop.right, layerState.geomCrop.top,
+                          layerState.geomBufferSize.getWidth(), layerState.geomCrop.bottom)));
+
+        activeTransparentRegion.orSelf(topRegion);
+        activeTransparentRegion.orSelf(bottomRegion);
+        activeTransparentRegion.orSelf(leftRegion);
+        activeTransparentRegion.orSelf(rightRegion);
     }
 
     // reduce uses a FloatRect to provide more accuracy during the
@@ -223,19 +225,22 @@
     // Some HWCs may clip client composited input to its displayFrame. Make sure
     // that this does not cut off the shadow.
     if (layerState.forceClientComposition && layerState.shadowSettings.length > 0.0f) {
-        const auto outset = layerState.shadowSettings.length;
+        // RenderEngine currently blurs shadows to smooth out edges, so outset by
+        // 2x the length instead of 1x to compensate
+        const auto outset = layerState.shadowSettings.length * 2;
         geomLayerBounds.left -= outset;
         geomLayerBounds.top -= outset;
         geomLayerBounds.right += outset;
         geomLayerBounds.bottom += outset;
     }
-    Rect frame{layerTransform.transform(reduce(geomLayerBounds, activeTransparentRegion))};
-    if (!frame.intersect(outputState.layerStackSpace.getContent(), &frame)) {
-        frame.clear();
-    }
-    const ui::Transform displayTransform{outputState.transform};
 
-    return displayTransform.transform(frame);
+    geomLayerBounds = layerTransform.transform(geomLayerBounds);
+    FloatRect frame = reduce(geomLayerBounds, activeTransparentRegion);
+    frame = frame.intersect(outputState.layerStackSpace.getContent().toFloatRect());
+
+    // convert from layerStackSpace to displaySpace
+    const ui::Transform displayTransform{outputState.transform};
+    return Rect(displayTransform.transform(frame));
 }
 
 uint32_t OutputLayer::calculateOutputRelativeBufferTransform(
@@ -844,6 +849,12 @@
     }
 }
 
+void OutputLayer::applyDeviceLayerLut(LutProperties /*lutProperties*/,
+                                      ndk::ScopedFileDescriptor /*lutPfd*/) {
+    // TODO(b/329472856): decode the shared memory of the pfd, and store the lut data into
+    // OutputLayerCompositionState#hwc struct
+}
+
 bool OutputLayer::needsFiltering() const {
     const auto& state = getState();
     const auto& sourceCrop = state.sourceCrop;
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 39163ea..9c0e62c 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -133,6 +133,7 @@
         MOCK_METHOD1(applyChangedTypesToLayers, void(const impl::Display::ChangedTypes&));
         MOCK_METHOD1(applyDisplayRequests, void(const impl::Display::DisplayRequests&));
         MOCK_METHOD1(applyLayerRequestsToLayers, void(const impl::Display::LayerRequests&));
+        MOCK_METHOD1(applyLayerLutsToLayers, void(const impl::Display::LayerLuts&));
 
         const compositionengine::CompositionEngine& mCompositionEngine;
         impl::OutputCompositionState mState;
@@ -212,6 +213,7 @@
               aidl::android::hardware::graphics::common::Dataspace::UNKNOWN},
              -1.f,
              DimmingStage::NONE},
+            {},
     };
 
     void chooseCompositionStrategy(Display* display) {
@@ -615,6 +617,7 @@
     EXPECT_CALL(*mDisplay, applyLayerRequestsToLayers(mDeviceRequestedChanges.layerRequests))
             .Times(1);
     EXPECT_CALL(*mDisplay, allLayersRequireClientComposition()).WillOnce(Return(false));
+    EXPECT_CALL(*mDisplay, applyLayerLutsToLayers(mDeviceRequestedChanges.layerLuts)).Times(1);
 
     chooseCompositionStrategy(mDisplay.get());
 
@@ -667,6 +670,7 @@
     EXPECT_CALL(*mDisplay, applyLayerRequestsToLayers(mDeviceRequestedChanges.layerRequests))
             .Times(1);
     EXPECT_CALL(*mDisplay, allLayersRequireClientComposition()).WillOnce(Return(false));
+    EXPECT_CALL(*mDisplay, applyLayerLutsToLayers(mDeviceRequestedChanges.layerLuts)).Times(1);
 
     chooseCompositionStrategy(mDisplay.get());
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index e910c72..5c55ce7 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -152,10 +152,7 @@
                 getOverlaySupport, (), (const, override));
     MOCK_METHOD(status_t, setRefreshRateChangedCallbackDebugEnabled, (PhysicalDisplayId, bool));
     MOCK_METHOD(status_t, notifyExpectedPresent, (PhysicalDisplayId, TimePoint, Fps));
-    MOCK_METHOD(status_t, getRequestedLuts,
-                (PhysicalDisplayId,
-                 std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*),
-                (override));
+    MOCK_METHOD((HWC2::Display::LutFileDescriptorMapper&), getLutFileDescriptorMapper, (), ());
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 1c54469..b21533a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -30,6 +30,7 @@
 #include "MockHWC2.h"
 #include "MockHWComposer.h"
 #include "RegionMatcher.h"
+#include "ui/FloatRect.h"
 
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 
@@ -270,7 +271,7 @@
         mLayerFEState.geomLayerTransform = ui::Transform{TR_IDENT};
         mLayerFEState.geomBufferSize = Rect{0, 0, 1920, 1080};
         mLayerFEState.geomBufferUsesDisplayInverseTransform = false;
-        mLayerFEState.geomCrop = Rect{0, 0, 1920, 1080};
+        mLayerFEState.geomCrop = FloatRect{0, 0, 1920, 1080};
         mLayerFEState.geomLayerBounds = FloatRect{0.f, 0.f, 1920.f, 1080.f};
 
         mOutputState.layerStackSpace.setContent(Rect{0, 0, 1920, 1080});
@@ -296,20 +297,20 @@
 }
 
 TEST_F(OutputLayerDisplayFrameTest, cropAffectsDisplayFrame) {
-    mLayerFEState.geomCrop = Rect{100, 200, 300, 500};
+    mLayerFEState.geomCrop = FloatRect{100, 200, 300, 500};
     const Rect expected{100, 200, 300, 500};
     EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
 TEST_F(OutputLayerDisplayFrameTest, cropAffectsDisplayFrameRotated) {
-    mLayerFEState.geomCrop = Rect{100, 200, 300, 500};
+    mLayerFEState.geomCrop = FloatRect{100, 200, 300, 500};
     mLayerFEState.geomLayerTransform.set(HAL_TRANSFORM_ROT_90, 1920, 1080);
     const Rect expected{1420, 100, 1720, 300};
     EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
 TEST_F(OutputLayerDisplayFrameTest, emptyGeomCropIsNotUsedToComputeFrame) {
-    mLayerFEState.geomCrop = Rect{};
+    mLayerFEState.geomCrop = FloatRect{};
     const Rect expected{0, 0, 1920, 1080};
     EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
@@ -339,7 +340,7 @@
 
     mLayerFEState.geomLayerBounds = FloatRect{100.f, 100.f, 200.f, 200.f};
     Rect expected{mLayerFEState.geomLayerBounds};
-    expected.inset(-kShadowRadius, -kShadowRadius, -kShadowRadius, -kShadowRadius);
+    expected.inset(-2 * kShadowRadius, -2 * kShadowRadius, -2 * kShadowRadius, -2 * kShadowRadius);
     EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 66237b9..77bd804 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -1547,7 +1547,8 @@
     return error;
 }
 
-Error AidlComposer::getRequestedLuts(Display display, std::vector<DisplayLuts::LayerLut>* outLuts) {
+Error AidlComposer::getRequestedLuts(Display display, std::vector<Layer>* outLayers,
+                                     std::vector<DisplayLuts::LayerLut>* outLuts) {
     Error error = Error::NONE;
     mMutex.lock_shared();
     if (auto reader = getReader(display)) {
@@ -1556,6 +1557,11 @@
         error = Error::BAD_DISPLAY;
     }
     mMutex.unlock_shared();
+
+    outLayers->reserve(outLuts->size());
+    for (const auto& layerLut : *outLuts) {
+        outLayers->emplace_back(translate<Layer>(layerLut.layer));
+    }
     return error;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 246223a..cdb67e4 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -245,7 +245,7 @@
     Error notifyExpectedPresent(Display, nsecs_t expectedPresentTime,
                                 int32_t frameIntervalNs) override;
     Error getRequestedLuts(
-            Display display,
+            Display display, std::vector<Layer>* outLayers,
             std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*
                     outLuts) override;
     Error setLayerLuts(
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 7db9a94..0905663 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -305,7 +305,7 @@
     virtual Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) = 0;
     virtual Error notifyExpectedPresent(Display, nsecs_t expectedPresentTime,
                                         int32_t frameIntervalNs) = 0;
-    virtual Error getRequestedLuts(Display display,
+    virtual Error getRequestedLuts(Display display, std::vector<Layer>* outLayers,
                                    std::vector<V3_0::DisplayLuts::LayerLut>* outLuts) = 0;
     virtual Error setLayerLuts(Display display, Layer layer, std::vector<V3_0::Lut>& luts) = 0;
 };
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index f1fa938..1df2ab1 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -609,17 +609,29 @@
     return static_cast<Error>(error);
 }
 
-Error Display::getRequestedLuts(std::vector<DisplayLuts::LayerLut>* outLayerLuts) {
-    std::vector<DisplayLuts::LayerLut> tmpLayerLuts;
-    const auto error = mComposer.getRequestedLuts(mId, &tmpLayerLuts);
-    for (DisplayLuts::LayerLut& layerLut : tmpLayerLuts) {
-        if (layerLut.lut.pfd.get() >= 0) {
-            outLayerLuts->push_back({layerLut.layer,
-                                     Lut{ndk::ScopedFileDescriptor(layerLut.lut.pfd.release()),
-                                         layerLut.lut.lutProperties}});
+Error Display::getRequestedLuts(LayerLuts* outLuts,
+                                LutFileDescriptorMapper& lutFileDescriptorMapper) {
+    std::vector<Hwc2::Layer> layerIds;
+    std::vector<DisplayLuts::LayerLut> tmpLuts;
+    const auto error = static_cast<Error>(mComposer.getRequestedLuts(mId, &layerIds, &tmpLuts));
+    if (error != Error::NONE) {
+        return error;
+    }
+
+    uint32_t numElements = layerIds.size();
+    outLuts->clear();
+    for (uint32_t i = 0; i < numElements; ++i) {
+        auto layer = getLayerById(layerIds[i]);
+        if (layer) {
+            auto& layerLut = tmpLuts[i];
+            outLuts->emplace_or_replace(layer.get(), layerLut.lut.lutProperties);
+            lutFileDescriptorMapper.emplace_or_replace(layer.get(),
+                                                       ndk::ScopedFileDescriptor(
+                                                               layerLut.lut.pfd.release()));
         }
     }
-    return static_cast<Error>(error);
+
+    return Error::NONE;
 }
 
 Error Display::getDisplayDecorationSupport(
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 8e2aeaf..61f92f4 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -20,6 +20,7 @@
 #include <android-base/thread_annotations.h>
 #include <ftl/expected.h>
 #include <ftl/future.h>
+#include <ftl/small_map.h>
 #include <gui/HdrMetadata.h>
 #include <math/mat4.h>
 #include <ui/HdrCapabilities.h>
@@ -107,6 +108,13 @@
     virtual void onLayerDestroyed(hal::HWLayerId layerId) = 0;
     virtual std::optional<ui::Size> getPhysicalSizeInMm() const = 0;
 
+    static const int kLutFileDescriptorMapperSize = 20;
+    using LayerLuts =
+            ftl::SmallMap<HWC2::Layer*, aidl::android::hardware::graphics::composer3::LutProperties,
+                          kLutFileDescriptorMapperSize>;
+    using LutFileDescriptorMapper =
+            ftl::SmallMap<HWC2::Layer*, ndk::ScopedFileDescriptor, kLutFileDescriptorMapperSize>;
+
     [[nodiscard]] virtual hal::Error acceptChanges() = 0;
     [[nodiscard]] virtual base::expected<std::shared_ptr<HWC2::Layer>, hal::Error>
     createLayer() = 0;
@@ -183,8 +191,7 @@
             aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness*
                     outClientTargetProperty) = 0;
     [[nodiscard]] virtual hal::Error getRequestedLuts(
-            std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*
-                    outLuts) = 0;
+            LayerLuts* outLuts, LutFileDescriptorMapper& lutFileDescriptorMapper) = 0;
     [[nodiscard]] virtual hal::Error getDisplayDecorationSupport(
             std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                     support) = 0;
@@ -268,9 +275,8 @@
     hal::Error getClientTargetProperty(
             aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness*
                     outClientTargetProperty) override;
-    hal::Error getRequestedLuts(
-            std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*
-                    outLuts) override;
+    hal::Error getRequestedLuts(LayerLuts* outLuts,
+                                LutFileDescriptorMapper& lutFileDescriptorMapper) override;
     hal::Error getDisplayDecorationSupport(
             std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                     support) override;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index d08e261..e37c0ba 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -587,9 +587,14 @@
     error = hwcDisplay->getClientTargetProperty(&clientTargetProperty);
     RETURN_IF_HWC_ERROR_FOR("getClientTargetProperty", error, displayId, BAD_INDEX);
 
+    DeviceRequestedChanges::LayerLuts layerLuts;
+    error = hwcDisplay->getRequestedLuts(&layerLuts, mLutFileDescriptorMapper);
+    RETURN_IF_HWC_ERROR_FOR("getRequestedLuts", error, displayId, BAD_INDEX);
+
     outChanges->emplace(DeviceRequestedChanges{std::move(changedTypes), std::move(displayRequests),
                                                std::move(layerRequests),
-                                               std::move(clientTargetProperty)});
+                                               std::move(clientTargetProperty),
+                                               std::move(layerLuts)});
     error = hwcDisplay->acceptChanges();
     RETURN_IF_HWC_ERROR_FOR("acceptChanges", error, displayId, BAD_INDEX);
 
@@ -978,21 +983,6 @@
     return NO_ERROR;
 }
 
-status_t HWComposer::getRequestedLuts(
-        PhysicalDisplayId displayId,
-        std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>* outLuts) {
-    RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
-    const auto error = mDisplayData[displayId].hwcDisplay->getRequestedLuts(outLuts);
-    if (error == hal::Error::UNSUPPORTED) {
-        RETURN_IF_HWC_ERROR(error, displayId, INVALID_OPERATION);
-    }
-    if (error == hal::Error::BAD_PARAMETER) {
-        RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
-    }
-    RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
-    return NO_ERROR;
-}
-
 status_t HWComposer::setAutoLowLatencyMode(PhysicalDisplayId displayId, bool on) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
     const auto error = mDisplayData[displayId].hwcDisplay->setAutoLowLatencyMode(on);
@@ -1036,6 +1026,11 @@
     return mSupportedLayerGenericMetadata;
 }
 
+ftl::SmallMap<HWC2::Layer*, ndk::ScopedFileDescriptor, 20>&
+HWComposer::getLutFileDescriptorMapper() {
+    return mLutFileDescriptorMapper;
+}
+
 void HWComposer::dumpOverlayProperties(std::string& result) const {
     // dump overlay properties
     result.append("OverlayProperties:\n");
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index b95c619..7b04d67 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -53,6 +53,7 @@
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayLuts.h>
+#include <aidl/android/hardware/graphics/composer3/LutProperties.h>
 #include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 
 namespace android {
@@ -90,11 +91,14 @@
                 aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness;
         using DisplayRequests = hal::DisplayRequest;
         using LayerRequests = std::unordered_map<HWC2::Layer*, hal::LayerRequest>;
+        using LutProperties = aidl::android::hardware::graphics::composer3::LutProperties;
+        using LayerLuts = HWC2::Display::LayerLuts;
 
         ChangedTypes changedTypes;
         DisplayRequests displayRequests;
         LayerRequests layerRequests;
         ClientTargetProperty clientTargetProperty;
+        LayerLuts layerLuts;
     };
 
     struct HWCDisplayMode {
@@ -311,18 +315,15 @@
     virtual status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) = 0;
     virtual status_t notifyExpectedPresent(PhysicalDisplayId, TimePoint expectedPresentTime,
                                            Fps frameInterval) = 0;
-
-    // Composer 4.0
-    virtual status_t getRequestedLuts(
-            PhysicalDisplayId,
-            std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*) = 0;
+    // mapper
+    virtual HWC2::Display::LutFileDescriptorMapper& getLutFileDescriptorMapper() = 0;
 };
 
 static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs,
                               const android::HWComposer::DeviceRequestedChanges& rhs) {
     return lhs.changedTypes == rhs.changedTypes && lhs.displayRequests == rhs.displayRequests &&
             lhs.layerRequests == rhs.layerRequests &&
-            lhs.clientTargetProperty == rhs.clientTargetProperty;
+            lhs.clientTargetProperty == rhs.clientTargetProperty && lhs.layerLuts == rhs.layerLuts;
 }
 
 namespace impl {
@@ -480,11 +481,8 @@
     status_t notifyExpectedPresent(PhysicalDisplayId, TimePoint expectedPresentTime,
                                    Fps frameInterval) override;
 
-    // Composer 4.0
-    status_t getRequestedLuts(
-            PhysicalDisplayId,
-            std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*)
-            override;
+    // get a mapper
+    HWC2::Display::LutFileDescriptorMapper& getLutFileDescriptorMapper() override;
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
@@ -571,6 +569,8 @@
     const size_t mMaxVirtualDisplayDimension;
     const bool mUpdateDeviceProductInfoOnHotplugReconnect;
     bool mEnableVrrTimeout;
+
+    HWC2::Display::LutFileDescriptorMapper mLutFileDescriptorMapper;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index ee1e07a..056ecd7 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -1410,7 +1410,8 @@
     return Error::NONE;
 }
 
-Error HidlComposer::getRequestedLuts(Display, std::vector<DisplayLuts::LayerLut>*) {
+Error HidlComposer::getRequestedLuts(Display, std::vector<Layer>*,
+                                     std::vector<DisplayLuts::LayerLut>*) {
     return Error::NONE;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 701a54b..1cc23d1 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -352,7 +352,7 @@
     Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override;
     Error notifyExpectedPresent(Display, nsecs_t, int32_t) override;
     Error getRequestedLuts(
-            Display,
+            Display, std::vector<Layer>*,
             std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*)
             override;
     Error setLayerLuts(Display, Layer,
diff --git a/services/surfaceflinger/EventLog/EventLog.cpp b/services/surfaceflinger/EventLog/EventLog.cpp
deleted file mode 100644
index 3b60952..0000000
--- a/services/surfaceflinger/EventLog/EventLog.cpp
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 2013 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.
- */
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <log/log.h>
-
-#include "EventLog.h"
-
-namespace android {
-
-ANDROID_SINGLETON_STATIC_INSTANCE(EventLog)
-
-
-EventLog::EventLog() {
-}
-
-void EventLog::doLogFrameDurations(const std::string_view& name, const int32_t* durations,
-                                   size_t numDurations) {
-    EventLog::TagBuffer buffer(LOGTAG_SF_FRAME_DUR);
-    buffer.startList(1 + numDurations);
-    buffer.writeString(name);
-    for (size_t i = 0; i < numDurations; i++) {
-        buffer.writeInt32(durations[i]);
-    }
-    buffer.endList();
-    buffer.log();
-}
-
-void EventLog::logFrameDurations(const std::string_view& name, const int32_t* durations,
-                                 size_t numDurations) {
-    EventLog::getInstance().doLogFrameDurations(name, durations, numDurations);
-}
-
-// ---------------------------------------------------------------------------
-
-EventLog::TagBuffer::TagBuffer(int32_t tag)
-    : mPos(0), mTag(tag), mOverflow(false) {
-}
-
-void EventLog::TagBuffer::log() {
-    if (mOverflow) {
-        ALOGW("couldn't log to binary event log: overflow.");
-    } else if (android_bWriteLog(mTag, mStorage, mPos) < 0) {
-        ALOGE("couldn't log to EventLog: %s", strerror(errno));
-    }
-    // purge the buffer
-    mPos = 0;
-    mOverflow = false;
-}
-
-void EventLog::TagBuffer::startList(int8_t count) {
-    if (mOverflow) return;
-    const size_t needed = 1 + sizeof(count);
-    if (mPos + needed > STORAGE_MAX_SIZE) {
-        mOverflow = true;
-        return;
-    }
-    mStorage[mPos + 0] = EVENT_TYPE_LIST;
-    mStorage[mPos + 1] = count;
-    mPos += needed;
-}
-
-void EventLog::TagBuffer::endList() {
-    if (mOverflow) return;
-    const size_t needed = 1;
-    if (mPos + needed > STORAGE_MAX_SIZE) {
-        mOverflow = true;
-        return;
-    }
-    mStorage[mPos + 0] = '\n';
-    mPos += needed;
-}
-
-void EventLog::TagBuffer::writeInt32(int32_t value) {
-    if (mOverflow) return;
-    const size_t needed = 1 + sizeof(value);
-    if (mPos + needed > STORAGE_MAX_SIZE) {
-        mOverflow = true;
-        return;
-    }
-    mStorage[mPos + 0] = EVENT_TYPE_INT;
-    memcpy(&mStorage[mPos + 1], &value, sizeof(value));
-    mPos += needed;
-}
-
-void EventLog::TagBuffer::writeInt64(int64_t value) {
-    if (mOverflow) return;
-    const size_t needed = 1 + sizeof(value);
-    if (mPos + needed > STORAGE_MAX_SIZE) {
-        mOverflow = true;
-        return;
-    }
-    mStorage[mPos + 0] = EVENT_TYPE_LONG;
-    memcpy(&mStorage[mPos + 1], &value, sizeof(value));
-    mPos += needed;
-}
-
-void EventLog::TagBuffer::writeString(const std::string_view& value) {
-    if (mOverflow) return;
-    const size_t stringLen = value.length();
-    const size_t needed = 1 + sizeof(int32_t) + stringLen;
-    if (mPos + needed > STORAGE_MAX_SIZE) {
-        mOverflow = true;
-        return;
-    }
-    mStorage[mPos + 0] = EVENT_TYPE_STRING;
-    memcpy(&mStorage[mPos + 1], &stringLen, sizeof(int32_t));
-    memcpy(&mStorage[mPos + 5], value.data(), stringLen);
-    mPos += needed;
-}
-
-} // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/EventLog/EventLog.h b/services/surfaceflinger/EventLog/EventLog.h
deleted file mode 100644
index ee3587e..0000000
--- a/services/surfaceflinger/EventLog/EventLog.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <utils/Errors.h>
-#include <utils/Singleton.h>
-
-#include <cstdint>
-#include <string_view>
-
-namespace android {
-
-class EventLog : public Singleton<EventLog> {
-
-public:
-    static void logFrameDurations(const std::string_view& name, const int32_t* durations,
-                                  size_t numDurations);
-
-protected:
-    EventLog();
-
-private:
-    /*
-     * EventLogBuffer is a helper class to construct an in-memory event log
-     * tag. In this version the buffer is not dynamic, so write operation can
-     * fail if there is not enough space in the temporary buffer.
-     * Once constructed, the buffer can be logger by calling the log()
-     * method.
-     */
-
-    class TagBuffer {
-        enum { STORAGE_MAX_SIZE = 128 };
-        int32_t mPos;
-        int32_t mTag;
-        bool mOverflow;
-        char mStorage[STORAGE_MAX_SIZE];
-    public:
-        explicit TagBuffer(int32_t tag);
-
-        void startList(int8_t count);
-        void endList();
-
-        void writeInt32(int32_t);
-        void writeInt64(int64_t);
-        void writeString(const std::string_view&);
-
-        void log();
-    };
-
-    friend class Singleton<EventLog>;
-    EventLog(const EventLog&);
-    EventLog& operator =(const EventLog&);
-
-    enum { LOGTAG_SF_FRAME_DUR = 60100 };
-    void doLogFrameDurations(const std::string_view& name, const int32_t* durations,
-                             size_t numDurations);
-};
-
-} // namespace android
diff --git a/services/surfaceflinger/EventLog/EventLogTags.logtags b/services/surfaceflinger/EventLog/EventLogTags.logtags
index 6c851dd..e68d9f5 100644
--- a/services/surfaceflinger/EventLog/EventLogTags.logtags
+++ b/services/surfaceflinger/EventLog/EventLogTags.logtags
@@ -35,7 +35,6 @@
 
 # 60100 - 60199 reserved for surfaceflinger
 
-60100 sf_frame_dur (window|3),(dur0|1),(dur1|1),(dur2|1),(dur3|1),(dur4|1),(dur5|1),(dur6|1)
 60110 sf_stop_bootanim (time|2|3)
 
 # NOTE - the range 1000000-2000000 is reserved for partners and others who
diff --git a/services/surfaceflinger/FrameTracker.cpp b/services/surfaceflinger/FrameTracker.cpp
index ca8cdc3..93d0313 100644
--- a/services/surfaceflinger/FrameTracker.cpp
+++ b/services/surfaceflinger/FrameTracker.cpp
@@ -26,16 +26,10 @@
 #include <ui/FrameStats.h>
 
 #include "FrameTracker.h"
-#include "EventLog/EventLog.h"
 
 namespace android {
 
-FrameTracker::FrameTracker() :
-        mOffset(0),
-        mNumFences(0),
-        mDisplayPeriod(0) {
-    resetFrameCountersLocked();
-}
+FrameTracker::FrameTracker() : mOffset(0), mNumFences(0), mDisplayPeriod(0) {}
 
 void FrameTracker::setDesiredPresentTime(nsecs_t presentTime) {
     Mutex::Autolock lock(mMutex);
@@ -73,9 +67,6 @@
 void FrameTracker::advanceFrame() {
     Mutex::Autolock lock(mMutex);
 
-    // Update the statistic to include the frame we just finished.
-    updateStatsLocked(mOffset);
-
     // Advance to the next frame.
     mOffset = (mOffset+1) % NUM_FRAME_RECORDS;
     mFrameRecords[mOffset].desiredPresentTime = INT64_MAX;
@@ -138,19 +129,12 @@
     }
 }
 
-void FrameTracker::logAndResetStats(const std::string_view& name) {
-    Mutex::Autolock lock(mMutex);
-    logStatsLocked(name);
-    resetFrameCountersLocked();
-}
-
 void FrameTracker::processFencesLocked() const {
     FrameRecord* records = const_cast<FrameRecord*>(mFrameRecords);
     int& numFences = const_cast<int&>(mNumFences);
 
     for (int i = 1; i < NUM_FRAME_RECORDS && numFences > 0; i++) {
-        size_t idx = (mOffset+NUM_FRAME_RECORDS-i) % NUM_FRAME_RECORDS;
-        bool updated = false;
+        size_t idx = (mOffset + NUM_FRAME_RECORDS - i) % NUM_FRAME_RECORDS;
 
         const std::shared_ptr<FenceTime>& rfence = records[idx].frameReadyFence;
         if (rfence != nullptr) {
@@ -158,7 +142,6 @@
             if (records[idx].frameReadyTime < INT64_MAX) {
                 records[idx].frameReadyFence = nullptr;
                 numFences--;
-                updated = true;
             }
         }
 
@@ -169,59 +152,8 @@
             if (records[idx].actualPresentTime < INT64_MAX) {
                 records[idx].actualPresentFence = nullptr;
                 numFences--;
-                updated = true;
             }
         }
-
-        if (updated) {
-            updateStatsLocked(idx);
-        }
-    }
-}
-
-void FrameTracker::updateStatsLocked(size_t newFrameIdx) const {
-    int* numFrames = const_cast<int*>(mNumFrames);
-
-    if (mDisplayPeriod > 0 && isFrameValidLocked(newFrameIdx)) {
-        size_t prevFrameIdx = (newFrameIdx+NUM_FRAME_RECORDS-1) %
-                NUM_FRAME_RECORDS;
-
-        if (isFrameValidLocked(prevFrameIdx)) {
-            nsecs_t newPresentTime =
-                    mFrameRecords[newFrameIdx].actualPresentTime;
-            nsecs_t prevPresentTime =
-                    mFrameRecords[prevFrameIdx].actualPresentTime;
-
-            nsecs_t duration = newPresentTime - prevPresentTime;
-            int numPeriods = int((duration + mDisplayPeriod/2) /
-                    mDisplayPeriod);
-
-            for (int i = 0; i < NUM_FRAME_BUCKETS-1; i++) {
-                int nextBucket = 1 << (i+1);
-                if (numPeriods < nextBucket) {
-                    numFrames[i]++;
-                    return;
-                }
-            }
-
-            // The last duration bucket is a catch-all.
-            numFrames[NUM_FRAME_BUCKETS-1]++;
-        }
-    }
-}
-
-void FrameTracker::resetFrameCountersLocked() {
-    for (int i = 0; i < NUM_FRAME_BUCKETS; i++) {
-        mNumFrames[i] = 0;
-    }
-}
-
-void FrameTracker::logStatsLocked(const std::string_view& name) const {
-    for (int i = 0; i < NUM_FRAME_BUCKETS; i++) {
-        if (mNumFrames[i] > 0) {
-            EventLog::logFrameDurations(name, mNumFrames, NUM_FRAME_BUCKETS);
-            return;
-        }
     }
 }
 
diff --git a/services/surfaceflinger/FrameTracker.h b/services/surfaceflinger/FrameTracker.h
index bc412ae..fd6fadc 100644
--- a/services/surfaceflinger/FrameTracker.h
+++ b/services/surfaceflinger/FrameTracker.h
@@ -41,8 +41,6 @@
     // frame time history.
     enum { NUM_FRAME_RECORDS = 128 };
 
-    enum { NUM_FRAME_BUCKETS = 7 };
-
     FrameTracker();
 
     // setDesiredPresentTime sets the time at which the current frame
@@ -142,13 +140,6 @@
     // doesn't grow with NUM_FRAME_RECORDS.
     int mNumFences;
 
-    // mNumFrames keeps a count of the number of frames with a duration in a
-    // particular range of vsync periods.  Element n of the array stores the
-    // number of frames with duration in the half-inclusive range
-    // [2^n, 2^(n+1)).  The last element of the array contains the count for
-    // all frames with duration greater than 2^(NUM_FRAME_BUCKETS-1).
-    int32_t mNumFrames[NUM_FRAME_BUCKETS];
-
     // mDisplayPeriod is the display refresh period of the display for which
     // this FrameTracker is gathering information.
     nsecs_t mDisplayPeriod;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 398e64a..b7d4cc5 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -72,7 +72,7 @@
     bool premultipliedAlpha;
     ui::Transform parentTransform;
     Rect bufferSize;
-    Rect croppedBufferSize;
+    FloatRect croppedBufferSize;
     std::shared_ptr<renderengine::ExternalTexture> externalTexture;
     gui::LayerMetadata layerMetadata;
     gui::LayerMetadata relativeLayerMetadata;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index ee605b7..10e212e 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -116,7 +116,7 @@
  * that's already included.
  */
 std::pair<FloatRect, bool> getInputBounds(const LayerSnapshot& snapshot, bool fillParentBounds) {
-    FloatRect inputBounds = snapshot.croppedBufferSize.toFloatRect();
+    FloatRect inputBounds = snapshot.croppedBufferSize;
     if (snapshot.hasBufferOrSidebandStream() && snapshot.croppedBufferSize.isValid() &&
         snapshot.localTransform.getType() != ui::Transform::IDENTITY) {
         inputBounds = snapshot.localTransform.transform(inputBounds);
@@ -220,7 +220,7 @@
     }
 
     // Check if the parent has cropped the buffer
-    Rect bufferSize = snapshot.croppedBufferSize;
+    FloatRect bufferSize = snapshot.croppedBufferSize;
     if (!bufferSize.isValid()) {
         snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
         return;
@@ -970,7 +970,7 @@
         parentRoundedCorner.radius.y *= t.getScaleY();
     }
 
-    FloatRect layerCropRect = snapshot.croppedBufferSize.toFloatRect();
+    FloatRect layerCropRect = snapshot.croppedBufferSize;
     const vec2 radius(requested.cornerRadius, requested.cornerRadius);
     RoundedCornerState layerSettings(layerCropRect, radius);
     const bool layerSettingsValid = layerSettings.hasRoundedCorners() && !layerCropRect.isEmpty();
@@ -1061,7 +1061,7 @@
             requested.externalTexture ? snapshot.bufferSize.toFloatRect() : parentBounds;
     snapshot.geomLayerCrop = parentBounds;
     if (!requested.crop.isEmpty()) {
-        snapshot.geomLayerCrop = snapshot.geomLayerCrop.intersect(requested.crop.toFloatRect());
+        snapshot.geomLayerCrop = snapshot.geomLayerCrop.intersect(requested.crop);
     }
     snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(snapshot.geomLayerCrop);
     snapshot.transformedBounds = snapshot.geomLayerTransform.transform(snapshot.geomLayerBounds);
@@ -1072,10 +1072,10 @@
             snapshot.geomLayerTransform.transform(geomLayerBoundsWithoutTransparentRegion);
     snapshot.parentTransform = parentSnapshot.geomLayerTransform;
 
-    // Subtract the transparent region and snap to the bounds
-    const Rect bounds =
-            RequestedLayerState::reduce(snapshot.croppedBufferSize, requested.transparentRegion);
     if (requested.potentialCursor) {
+        // Subtract the transparent region and snap to the bounds
+        const Rect bounds = RequestedLayerState::reduce(Rect(snapshot.croppedBufferSize),
+                                                        requested.transparentRegion);
         snapshot.cursorFrame = snapshot.geomLayerTransform.transform(bounds);
     }
 }
@@ -1192,7 +1192,8 @@
         snapshot.inputInfo.inputConfig |= InputConfig::TRUSTED_OVERLAY;
     }
 
-    snapshot.inputInfo.contentSize = snapshot.croppedBufferSize.getSize();
+    snapshot.inputInfo.contentSize = {snapshot.croppedBufferSize.getHeight(),
+                                      snapshot.croppedBufferSize.getWidth()};
 
     // If the layer is a clone, we need to crop the input region to cloned root to prevent
     // touches from going outside the cloned area.
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 5734ccf..1eff9d6 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -96,7 +96,7 @@
     LLOGV(layerId, "Created %s flags=%d", getDebugString().c_str(), flags);
     color.a = 1.0f;
 
-    crop.makeInvalid();
+    crop = {0, 0, -1, -1};
     z = 0;
     layerStack = ui::DEFAULT_LAYER_STACK;
     transformToDisplayInverse = false;
@@ -473,10 +473,10 @@
     return Rect(0, 0, static_cast<int32_t>(bufWidth), static_cast<int32_t>(bufHeight));
 }
 
-Rect RequestedLayerState::getCroppedBufferSize(const Rect& bufferSize) const {
-    Rect size = bufferSize;
+FloatRect RequestedLayerState::getCroppedBufferSize(const Rect& bufferSize) const {
+    FloatRect size = bufferSize.toFloatRect();
     if (!crop.isEmpty() && size.isValid()) {
-        size.intersect(crop, &size);
+        size = size.intersect(crop);
     } else if (!crop.isEmpty()) {
         size = crop;
     }
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 1d96dff..3220e86 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -79,7 +79,7 @@
     bool isHiddenByPolicy() const;
     half4 getColor() const;
     Rect getBufferSize(uint32_t displayRotationFlags) const;
-    Rect getCroppedBufferSize(const Rect& bufferSize) const;
+    FloatRect getCroppedBufferSize(const Rect& bufferSize) const;
     Rect getBufferCrop() const;
     std::string getDebugString() const;
     std::string getDebugStringShort() const;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index dcb0812..9289484 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -138,7 +138,7 @@
                 args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))) {
     ALOGV("Creating Layer %s", getDebugName());
 
-    mDrawingState.crop.makeInvalid();
+    mDrawingState.crop = {0, 0, -1, -1};
     mDrawingState.sequence = 0;
     mDrawingState.transform.set(0, 0);
     mDrawingState.frameNumber = 0;
@@ -183,7 +183,6 @@
     mFlinger->mTimeStats->onDestroy(layerId);
     mFlinger->mFrameTracer->onDestroy(layerId);
 
-    mFrameTracker.logAndResetStats(mName);
     mFlinger->onLayerDestroyed(this);
 
     if (mDrawingState.sidebandStream != nullptr) {
@@ -316,7 +315,7 @@
 
 Rect Layer::getCroppedBufferSize(const State& s) const {
     Rect size = getBufferSize(s);
-    Rect crop = getCrop(s);
+    Rect crop = Rect(getCrop(s));
     if (!crop.isEmpty() && size.isValid()) {
         size.intersect(crop, &size);
     } else if (!crop.isEmpty()) {
@@ -373,7 +372,7 @@
     mTransactionFlags |= mask;
 }
 
-bool Layer::setCrop(const Rect& crop) {
+bool Layer::setCrop(const FloatRect& crop) {
     if (mDrawingState.crop == crop) return false;
     mDrawingState.sequence++;
     mDrawingState.crop = crop;
@@ -605,10 +604,6 @@
     mFrameTracker.clearStats();
 }
 
-void Layer::logFrameStats() {
-    mFrameTracker.logAndResetStats(mName);
-}
-
 void Layer::getFrameStats(FrameStats* outStats) const {
     mFrameTracker.getStats(outStats);
 }
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 9bc557e..57093ae 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -95,7 +95,7 @@
     struct State {
         int32_t sequence; // changes when visible regions can change
         // Crop is expressed in layer space coordinate.
-        Rect crop;
+        FloatRect crop;
         LayerMetadata metadata;
 
         ui::Dataspace dataspace;
@@ -172,7 +172,7 @@
     // be delayed until the resize completes.
 
     // Buffer space
-    bool setCrop(const Rect& crop);
+    bool setCrop(const FloatRect& crop);
 
     bool setTransform(uint32_t /*transform*/);
     bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/);
@@ -198,7 +198,7 @@
     Region getVisibleRegion(const DisplayDevice*) const;
     void updateLastLatchTime(nsecs_t latchtime);
 
-    Rect getCrop(const Layer::State& s) const { return s.crop; }
+    Rect getCrop(const Layer::State& s) const { return Rect(s.crop); }
 
     // from graphics API
     static ui::Dataspace translateDataspace(ui::Dataspace dataspace);
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index 5eea45b..a0fbab0 100644
--- a/services/surfaceflinger/LayerProtoHelper.cpp
+++ b/services/surfaceflinger/LayerProtoHelper.cpp
@@ -106,6 +106,13 @@
     outRect.right = proto.right();
 }
 
+void LayerProtoHelper::readFromProto(const perfetto::protos::RectProto& proto, FloatRect& outRect) {
+    outRect.left = proto.left();
+    outRect.top = proto.top();
+    outRect.bottom = proto.bottom();
+    outRect.right = proto.right();
+}
+
 void LayerProtoHelper::writeToProto(
         const FloatRect& rect,
         std::function<perfetto::protos::FloatRectProto*()> getFloatRectProto) {
@@ -427,7 +434,7 @@
                                        layerInfo->mutable_color_transform());
     }
 
-    LayerProtoHelper::writeToProto(snapshot.croppedBufferSize.toFloatRect(),
+    LayerProtoHelper::writeToProto(snapshot.croppedBufferSize,
                                    [&]() { return layerInfo->mutable_source_bounds(); });
     LayerProtoHelper::writeToProto(snapshot.transformedBounds,
                                    [&]() { return layerInfo->mutable_screen_bounds(); });
@@ -455,7 +462,7 @@
         return layerInfo->mutable_requested_position();
     });
 
-    LayerProtoHelper::writeToProto(requestedState.crop,
+    LayerProtoHelper::writeToProto(Rect(requestedState.crop),
                                    [&]() { return layerInfo->mutable_crop(); });
 
     layerInfo->set_is_opaque(snapshot.contentOpaque);
diff --git a/services/surfaceflinger/LayerProtoHelper.h b/services/surfaceflinger/LayerProtoHelper.h
index 41ea684..3ca553a 100644
--- a/services/surfaceflinger/LayerProtoHelper.h
+++ b/services/surfaceflinger/LayerProtoHelper.h
@@ -44,6 +44,7 @@
                              std::function<perfetto::protos::RectProto*()> getRectProto);
     static void writeToProto(const Rect& rect, perfetto::protos::RectProto* rectProto);
     static void readFromProto(const perfetto::protos::RectProto& proto, Rect& outRect);
+    static void readFromProto(const perfetto::protos::RectProto& proto, FloatRect& outRect);
     static void writeToProto(const FloatRect& rect,
                              std::function<perfetto::protos::FloatRectProto*()> getFloatRectProto);
     static void writeToProto(const Region& region,
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 218c56e..e385f18 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -426,8 +426,8 @@
 
     LOG_FATAL_IF(!mVSyncState);
     mVsyncTracer = (mVsyncTracer + 1) % 2;
-    mPendingEvents.push_back(makeVSync(mVSyncState->displayId, wakeupTime, ++mVSyncState->count,
-                                       vsyncTime, readyTime));
+    mPendingEvents.push_back(makeVSync(mVsyncSchedule->getPhysicalDisplayId(), wakeupTime,
+                                       ++mVSyncState->count, vsyncTime, readyTime));
     mCondition.notify_all();
 }
 
@@ -486,9 +486,9 @@
             if (event->header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG) {
                 if (event->hotplug.connectionError == 0) {
                     if (event->hotplug.connected && !mVSyncState) {
-                        mVSyncState.emplace(event->header.displayId);
-                    } else if (!event->hotplug.connected && mVSyncState &&
-                               mVSyncState->displayId == event->header.displayId) {
+                        mVSyncState.emplace();
+                    } else if (!event->hotplug.connected &&
+                               mVsyncSchedule->getPhysicalDisplayId() == event->header.displayId) {
                         mVSyncState.reset();
                     }
                 } else {
@@ -559,7 +559,7 @@
                 const auto now = systemTime(SYSTEM_TIME_MONOTONIC);
                 const auto deadlineTimestamp = now + timeout.count();
                 const auto expectedVSyncTime = deadlineTimestamp + timeout.count();
-                mPendingEvents.push_back(makeVSync(mVSyncState->displayId, now,
+                mPendingEvents.push_back(makeVSync(mVsyncSchedule->getPhysicalDisplayId(), now,
                                                    ++mVSyncState->count, expectedVSyncTime,
                                                    deadlineTimestamp));
             }
@@ -739,7 +739,7 @@
     StringAppendF(&result, "%s: state=%s VSyncState=", mThreadName, toCString(mState));
     if (mVSyncState) {
         StringAppendF(&result, "{displayId=%s, count=%u%s}\n",
-                      to_string(mVSyncState->displayId).c_str(), mVSyncState->count,
+                      to_string(mVsyncSchedule->getPhysicalDisplayId()).c_str(), mVSyncState->count,
                       mVSyncState->synthetic ? ", synthetic" : "");
     } else {
         StringAppendF(&result, "none\n");
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index bbe4f9d..c3c7eb0 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -235,10 +235,6 @@
 
     // VSYNC state of connected display.
     struct VSyncState {
-        explicit VSyncState(PhysicalDisplayId displayId) : displayId(displayId) {}
-
-        const PhysicalDisplayId displayId;
-
         // Number of VSYNC events since display was connected.
         uint32_t count = 0;
 
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 881d678..e63cbb2 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -112,6 +112,8 @@
 
     bool getPendingHardwareVsyncState() const REQUIRES(kMainThreadContext);
 
+    PhysicalDisplayId getPhysicalDisplayId() const { return mId; }
+
 protected:
     using ControllerPtr = std::unique_ptr<VsyncController>;
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 8d16b9f..5010ba1 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1654,6 +1654,22 @@
         outProperties->combinations.emplace_back(outCombination);
     }
     outProperties->supportMixedColorSpaces = aidlProperties.supportMixedColorSpaces;
+    if (aidlProperties.lutProperties.has_value()) {
+        std::vector<gui::LutProperties> outLutProperties;
+        for (const auto& properties : aidlProperties.lutProperties.value()) {
+            gui::LutProperties currentProperties;
+            currentProperties.dimension =
+                    static_cast<gui::LutProperties::Dimension>(properties->dimension);
+            currentProperties.size = properties->size;
+            currentProperties.samplingKeys.reserve(properties->samplingKeys.size());
+            std::transform(properties->samplingKeys.cbegin(), properties->samplingKeys.cend(),
+                           std::back_inserter(currentProperties.samplingKeys), [](const auto& val) {
+                               return static_cast<gui::LutProperties::SamplingKey>(val);
+                           });
+            outLutProperties.push_back(std::move(currentProperties));
+        }
+        outProperties->lutProperties.emplace(outLutProperties.begin(), outLutProperties.end());
+    }
     return NO_ERROR;
 }
 
@@ -3268,8 +3284,6 @@
         // getTotalSize returns the total number of buffers that were allocated by SurfaceFlinger
         SFTRACE_INT64("Total Buffer Size", GraphicBufferAllocator::get().getTotalSize());
     }
-
-    logFrameStats(presentTime);
 }
 
 void SurfaceFlinger::commitTransactions() {
@@ -5741,7 +5755,7 @@
 
 void SurfaceFlinger::dumpFrontEnd(std::string& result) {
     std::ostringstream out;
-    out << "\nComposition list\n";
+    out << "\nComposition list (bottom to top)\n";
     ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
     for (const auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
         if (lastPrintedLayerStackHeader != snapshot->outputFilter.layerStack) {
@@ -5769,7 +5783,7 @@
 
 void SurfaceFlinger::dumpVisibleFrontEnd(std::string& result) {
     std::ostringstream out;
-    out << "\nComposition list\n";
+    out << "\nComposition list (bottom to top)\n";
     ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
     mLayerSnapshotBuilder.forEachVisibleSnapshot(
             [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index b189598..f39a4d2 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -147,7 +147,7 @@
         proto.set_transform_to_display_inverse(layer.transformToDisplayInverse);
     }
     if (layer.what & layer_state_t::eCropChanged) {
-        LayerProtoHelper::writeToProto(layer.crop, proto.mutable_crop());
+        LayerProtoHelper::writeToProto(Rect(layer.crop), proto.mutable_crop());
     }
     if (layer.what & layer_state_t::eBufferChanged) {
         perfetto::protos::LayerState_BufferData* bufferProto = proto.mutable_buffer_data();
diff --git a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
index ae380ad..b472047 100644
--- a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
+++ b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
@@ -182,7 +182,7 @@
         mLifecycleManager.applyTransactions(setZTransaction(id, z));
     }
 
-    void setCrop(uint32_t id, const Rect& crop) {
+    void setCrop(uint32_t id, const FloatRect& crop) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
@@ -193,6 +193,8 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setCrop(uint32_t id, const Rect& crop) { setCrop(id, crop.toFloatRect()); }
+
     void setFlags(uint32_t id, uint32_t mask, uint32_t flags) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 23d3c16..4f72424 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -467,7 +467,7 @@
                                                          LayerProperties::FORMAT,
                                                          LayerProperties::USAGE |
                                                                  GraphicBuffer::USAGE_HW_TEXTURE);
-        layer.crop = Rect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
+        layer.crop = FloatRect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
         layer.externalTexture = buffer;
         layer.bufferData->acquireFence = Fence::NO_FENCE;
         layer.dataspace = ui::Dataspace::UNKNOWN;
@@ -664,7 +664,8 @@
                 NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
                                      false);
         layer.sidebandStream = stream;
-        layer.crop = Rect(0, 0, SidebandLayerProperties::HEIGHT, SidebandLayerProperties::WIDTH);
+        layer.crop =
+                FloatRect(0, 0, SidebandLayerProperties::HEIGHT, SidebandLayerProperties::WIDTH);
     }
 
     static void setupHwcSetSourceCropBufferCallExpectations(CompositionTest* test) {
@@ -828,7 +829,7 @@
             return frontend::RequestedLayerState(args);
         });
 
-        layer.crop = Rect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
+        layer.crop = FloatRect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
         return layer;
     }
 
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 75d2fa3..a35ae15 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -162,12 +162,12 @@
     info.info.logicalHeight = 100;
     info.info.logicalWidth = 200;
     mFrontEndDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(1), info);
-    Rect layerCrop(0, 0, 10, 20);
+    FloatRect layerCrop(0, 0, 10, 20);
     setCrop(11, layerCrop);
     EXPECT_TRUE(mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Geometry));
     UPDATE_AND_VERIFY_WITH_DISPLAY_CHANGES(mSnapshotBuilder, STARTING_ZORDER);
     EXPECT_EQ(getSnapshot(11)->geomCrop, layerCrop);
-    EXPECT_EQ(getSnapshot(111)->geomLayerBounds, layerCrop.toFloatRect());
+    EXPECT_EQ(getSnapshot(111)->geomLayerBounds, layerCrop);
     float maxHeight = static_cast<float>(info.info.logicalHeight * 10);
     float maxWidth = static_cast<float>(info.info.logicalWidth * 10);
 
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 9efe73d..adbd868 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -100,7 +100,9 @@
     const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
 
     using RefreshRateSelector::GetRankedFrameRatesCache;
-    auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; }
+    auto& mutableGetRankedRefreshRatesCache() NO_THREAD_SAFETY_ANALYSIS {
+        return mGetRankedFrameRatesCache;
+    }
 
     auto getRankedFrameRates(const std::vector<LayerRequirement>& layers,
                              GlobalSignals signals = {}, Fps pacesetterFps = {}) const {
@@ -138,7 +140,9 @@
         return setPolicy(policy);
     }
 
-    const auto& getPrimaryFrameRates() const { return mPrimaryFrameRates; }
+    const auto& getPrimaryFrameRates() const NO_THREAD_SAFETY_ANALYSIS {
+        return mPrimaryFrameRates;
+    }
 };
 
 class RefreshRateSelectorTest : public testing::TestWithParam<Config::FrameRateOverride> {
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index fab1f6d..1e8cd0a 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -387,7 +387,7 @@
 
         state.state.what = what;
         if (what & layer_state_t::eCropChanged) {
-            state.state.crop = Rect(1, 2, 3, 4);
+            state.state.crop = FloatRect(1, 2, 3, 4);
         }
         if (what & layer_state_t::eFlagsChanged) {
             state.state.flags = layer_state_t::eEnableBackpressure;
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index f472d8f..615cc94 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -182,7 +182,7 @@
     MOCK_METHOD(Error, notifyExpectedPresent, (Display, nsecs_t, int32_t));
     MOCK_METHOD(
             Error, getRequestedLuts,
-            (Display,
+            (Display, std::vector<Layer>*,
              std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*));
     MOCK_METHOD(Error, setLayerLuts,
                 (Display, Layer, std::vector<aidl::android::hardware::graphics::composer3::Lut>&));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index 5edd2cd..53ed2e1 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -111,7 +111,7 @@
                 (aidl::android::hardware::graphics::composer3::OverlayProperties *),
                 (const override));
     MOCK_METHOD(hal::Error, getRequestedLuts,
-                (std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*),
+                ((HWC2::Display::LayerLuts*), (HWC2::Display::LutFileDescriptorMapper&)),
                 (override));
 };
 
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index 4ac1618..b06ee3b 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -107,6 +107,10 @@
         mInfoCache.mMaxEnvelopeEffectControlPointDuration =
                 getMaxEnvelopeEffectControlPointDurationInternal();
     }
+    if (mInfoCache.mFrequencyToOutputAccelerationMap.isFailed()) {
+        mInfoCache.mFrequencyToOutputAccelerationMap =
+                getFrequencyToOutputAccelerationMapInternal();
+    }
     return mInfoCache.get();
 }
 
@@ -239,6 +243,13 @@
     return HalResult<milliseconds>::unsupported();
 }
 
+HalResult<std::vector<PwleV2OutputMapEntry>>
+HalWrapper::getFrequencyToOutputAccelerationMapInternal() {
+    ALOGV("Skipped getFrequencyToOutputAccelerationMapInternal because it's not "
+          "available in Vibrator HAL");
+    return HalResult<std::vector<PwleV2OutputMapEntry>>::unsupported();
+}
+
 // -------------------------------------------------------------------------------------------------
 
 HalResult<void> AidlHalWrapper::ping() {
@@ -487,6 +498,15 @@
     return HalResultFactory::fromStatus<milliseconds>(std::move(status), milliseconds(durationMs));
 }
 
+HalResult<std::vector<PwleV2OutputMapEntry>>
+AidlHalWrapper::getFrequencyToOutputAccelerationMapInternal() {
+    std::vector<PwleV2OutputMapEntry> frequencyToOutputAccelerationMap;
+    auto status =
+            getHal()->getPwleV2FrequencyToOutputAccelerationMap(&frequencyToOutputAccelerationMap);
+    return HalResultFactory::fromStatus<
+            std::vector<PwleV2OutputMapEntry>>(std::move(status), frequencyToOutputAccelerationMap);
+}
+
 std::shared_ptr<Aidl::IVibrator> AidlHalWrapper::getHal() {
     std::lock_guard<std::mutex> lock(mHandleMutex);
     return mHandle;
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index 4938b15..b2bfffc 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -243,6 +243,7 @@
     using EffectStrength = aidl::android::hardware::vibrator::EffectStrength;
     using CompositePrimitive = aidl::android::hardware::vibrator::CompositePrimitive;
     using Braking = aidl::android::hardware::vibrator::Braking;
+    using PwleV2OutputMapEntry = aidl::android::hardware::vibrator::PwleV2OutputMapEntry;
 
     const HalResult<Capabilities> capabilities;
     const HalResult<std::vector<Effect>> supportedEffects;
@@ -261,6 +262,7 @@
     const HalResult<int32_t> maxEnvelopeEffectSize;
     const HalResult<std::chrono::milliseconds> minEnvelopeEffectControlPointDuration;
     const HalResult<std::chrono::milliseconds> maxEnvelopeEffectControlPointDuration;
+    const HalResult<std::vector<PwleV2OutputMapEntry>> frequencyToOutputAccelerationMap;
 
     void logFailures() const {
         logFailure<Capabilities>(capabilities, "getCapabilities");
@@ -284,6 +286,8 @@
                                               "getMinEnvelopeEffectControlPointDuration");
         logFailure<std::chrono::milliseconds>(maxEnvelopeEffectControlPointDuration,
                                               "getMaxEnvelopeEffectControlPointDuration");
+        logFailure<std::vector<PwleV2OutputMapEntry>>(frequencyToOutputAccelerationMap,
+                                                      "getfrequencyToOutputAccelerationMap");
     }
 
     bool shouldRetry() const {
@@ -296,7 +300,8 @@
                 qFactor.shouldRetry() || maxAmplitudes.shouldRetry() ||
                 maxEnvelopeEffectSize.shouldRetry() ||
                 minEnvelopeEffectControlPointDuration.shouldRetry() ||
-                maxEnvelopeEffectControlPointDuration.shouldRetry();
+                maxEnvelopeEffectControlPointDuration.shouldRetry() ||
+                frequencyToOutputAccelerationMap.shouldRetry();
     }
 
 private:
@@ -327,7 +332,8 @@
                 mMaxAmplitudes,
                 mMaxEnvelopeEffectSize,
                 mMinEnvelopeEffectControlPointDuration,
-                mMaxEnvelopeEffectControlPointDuration};
+                mMaxEnvelopeEffectControlPointDuration,
+                mFrequencyToOutputAccelerationMap};
     }
 
 private:
@@ -359,6 +365,8 @@
             HalResult<std::chrono::milliseconds>::transactionFailed(MSG);
     HalResult<std::chrono::milliseconds> mMaxEnvelopeEffectControlPointDuration =
             HalResult<std::chrono::milliseconds>::transactionFailed(MSG);
+    HalResult<std::vector<Info::PwleV2OutputMapEntry>> mFrequencyToOutputAccelerationMap =
+            HalResult<std::vector<Info::PwleV2OutputMapEntry>>::transactionFailed(MSG);
 
     friend class HalWrapper;
 };
@@ -442,6 +450,8 @@
     virtual HalResult<int32_t> getMaxEnvelopeEffectSizeInternal();
     virtual HalResult<std::chrono::milliseconds> getMinEnvelopeEffectControlPointDurationInternal();
     virtual HalResult<std::chrono::milliseconds> getMaxEnvelopeEffectControlPointDurationInternal();
+    virtual HalResult<std::vector<PwleV2OutputMapEntry>>
+    getFrequencyToOutputAccelerationMapInternal();
 
 private:
     std::mutex mInfoMutex;
@@ -518,12 +528,12 @@
     HalResult<float> getQFactorInternal() override final;
     HalResult<std::vector<float>> getMaxAmplitudesInternal() override final;
     HalResult<int32_t> getMaxEnvelopeEffectSizeInternal() override final;
-
     HalResult<std::chrono::milliseconds> getMinEnvelopeEffectControlPointDurationInternal()
             override final;
-
     HalResult<std::chrono::milliseconds> getMaxEnvelopeEffectControlPointDurationInternal()
             override final;
+    HalResult<std::vector<PwleV2OutputMapEntry>> getFrequencyToOutputAccelerationMapInternal()
+            override final;
 
 private:
     const reconnect_fn mReconnectFn;
diff --git a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
index 17f384d..d42aa56 100644
--- a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
@@ -39,6 +39,7 @@
 using aidl::android::hardware::vibrator::IVibrator;
 using aidl::android::hardware::vibrator::IVibratorCallback;
 using aidl::android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::PwleV2OutputMapEntry;
 using aidl::android::hardware::vibrator::PwleV2Primitive;
 using aidl::android::hardware::vibrator::VendorEffect;
 using aidl::android::os::PersistableBundle;
@@ -242,6 +243,11 @@
     std::vector<CompositePrimitive> supportedPrimitives = {CompositePrimitive::CLICK};
     std::vector<Braking> supportedBraking = {Braking::CLAB};
     std::vector<float> amplitudes = {0.f, 1.f, 0.f};
+    std::vector<PwleV2OutputMapEntry>
+            frequencyToOutputAccelerationMap{PwleV2OutputMapEntry(/*frequency=*/30.0f,
+                                                                  /*maxOutputAcceleration=*/0.2),
+                                             PwleV2OutputMapEntry(/*frequency=*/60.0f,
+                                                                  /*maxOutputAcceleration=*/0.8)};
 
     std::vector<std::chrono::milliseconds> primitiveDurations;
     constexpr auto primitiveRange = ndk::enum_range<CompositePrimitive>();
@@ -323,6 +329,11 @@
             .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
             .WillOnce(DoAll(SetArgPointee<0>(PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS),
                             Return(ndk::ScopedAStatus::ok())));
+    EXPECT_CALL(*mMockHal.get(), getPwleV2FrequencyToOutputAccelerationMap(_))
+            .Times(Exactly(2))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(frequencyToOutputAccelerationMap),
+                            Return(ndk::ScopedAStatus::ok())));
 
     vibrator::Info failed = mWrapper->getInfo();
     ASSERT_TRUE(failed.capabilities.isFailed());
@@ -342,6 +353,7 @@
     ASSERT_TRUE(failed.maxEnvelopeEffectSize.isFailed());
     ASSERT_TRUE(failed.minEnvelopeEffectControlPointDuration.isFailed());
     ASSERT_TRUE(failed.maxEnvelopeEffectControlPointDuration.isFailed());
+    ASSERT_TRUE(failed.frequencyToOutputAccelerationMap.isFailed());
 
     vibrator::Info successful = mWrapper->getInfo();
     ASSERT_EQ(vibrator::Capabilities::ON_CALLBACK, successful.capabilities.value());
@@ -364,6 +376,8 @@
               successful.minEnvelopeEffectControlPointDuration.value());
     ASSERT_EQ(std::chrono::milliseconds(PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS),
               successful.maxEnvelopeEffectControlPointDuration.value());
+    ASSERT_EQ(frequencyToOutputAccelerationMap,
+              successful.frequencyToOutputAccelerationMap.value());
 }
 
 TEST_F(VibratorHalWrapperAidlTest, TestGetInfoCachesResult) {
@@ -377,6 +391,11 @@
     constexpr int32_t PWLE_V2_MAX_ALLOWED_PRIMITIVE_MIN_DURATION_MS = 20;
     constexpr int32_t PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS = 1000;
     std::vector<Effect> supportedEffects = {Effect::CLICK, Effect::TICK};
+    std::vector<PwleV2OutputMapEntry>
+            frequencyToOutputAccelerationMap{PwleV2OutputMapEntry(/*frequency=*/30.0f,
+                                                                  /*maxOutputAcceleration=*/0.2),
+                                             PwleV2OutputMapEntry(/*frequency=*/60.0f,
+                                                                  /*maxOutputAcceleration=*/0.8)};
 
     EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
             .Times(Exactly(1))
@@ -432,6 +451,10 @@
             .Times(Exactly(1))
             .WillOnce(DoAll(SetArgPointee<0>(PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS),
                             Return(ndk::ScopedAStatus::ok())));
+    EXPECT_CALL(*mMockHal.get(), getPwleV2FrequencyToOutputAccelerationMap(_))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<0>(frequencyToOutputAccelerationMap),
+                            Return(ndk::ScopedAStatus::ok())));
 
     std::vector<std::thread> threads;
     for (int i = 0; i < 10; i++) {
@@ -460,6 +483,7 @@
               info.minEnvelopeEffectControlPointDuration.value());
     ASSERT_EQ(std::chrono::milliseconds(PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS),
               info.maxEnvelopeEffectControlPointDuration.value());
+    ASSERT_EQ(frequencyToOutputAccelerationMap, info.frequencyToOutputAccelerationMap.value());
 }
 
 TEST_F(VibratorHalWrapperAidlTest, TestPerformEffectWithCallbackSupport) {
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
index a09ddec..d6dab8d 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
@@ -223,6 +223,7 @@
     ASSERT_TRUE(info.maxEnvelopeEffectSize.isUnsupported());
     ASSERT_TRUE(info.minEnvelopeEffectControlPointDuration.isUnsupported());
     ASSERT_TRUE(info.maxEnvelopeEffectControlPointDuration.isUnsupported());
+    ASSERT_TRUE(info.frequencyToOutputAccelerationMap.isUnsupported());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_0Test, TestGetInfoWithoutAmplitudeControl) {
@@ -259,6 +260,7 @@
     ASSERT_TRUE(info.maxEnvelopeEffectSize.isUnsupported());
     ASSERT_TRUE(info.minEnvelopeEffectControlPointDuration.isUnsupported());
     ASSERT_TRUE(info.maxEnvelopeEffectControlPointDuration.isUnsupported());
+    ASSERT_TRUE(info.frequencyToOutputAccelerationMap.isUnsupported());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_0Test, TestPerformEffect) {