Merge changes from topic "getsuggestedframerate" into main

* changes:
  SF: Adds support for getSuggestedFrameRate api
  Adds support for getSuggestedFrameRate api
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 9c01169..07d16f7 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -4,9 +4,6 @@
       "name": "SurfaceFlinger_test",
       "options": [
         {
-          "include-filter": "*"
-        },
-        {
           // TODO(b/305717998): Deflake and re-enable
           "exclude-filter": "*ChildLayerTest*"
         }
@@ -23,12 +20,7 @@
   ],
   "hwasan-postsubmit": [
     {
-      "name": "SurfaceFlinger_test",
-      "options": [
-        {
-          "include-filter": "*"
-        }
-      ]
+      "name": "SurfaceFlinger_test"
     }
   ]
 }
diff --git a/data/etc/android.hardware.xr.input.controller.xml b/data/etc/android.hardware.xr.input.controller.xml
new file mode 100644
index 0000000..1fb8b41
--- /dev/null
+++ b/data/etc/android.hardware.xr.input.controller.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature to indicate the device supports
+     input from XR controllers. -->
+<permissions>
+    <feature name="android.hardware.xr.input.controller" />
+</permissions>
diff --git a/data/etc/android.hardware.xr.input.eye_tracking.xml b/data/etc/android.hardware.xr.input.eye_tracking.xml
new file mode 100644
index 0000000..8c6c2ed
--- /dev/null
+++ b/data/etc/android.hardware.xr.input.eye_tracking.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature to indicate the device supports
+     input from an XR user's eyes. -->
+<permissions>
+    <feature name="android.hardware.xr.input.eye_tracking" />
+</permissions>
diff --git a/data/etc/android.hardware.xr.input.hand_tracking.xml b/data/etc/android.hardware.xr.input.hand_tracking.xml
new file mode 100644
index 0000000..6de3bee
--- /dev/null
+++ b/data/etc/android.hardware.xr.input.hand_tracking.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature to indicate the device supports
+     input from an XR user's hands. -->
+<permissions>
+    <feature name="android.hardware.xr.input.hand_tracking" />
+</permissions>
diff --git a/data/etc/android.software.xr.api.openxr-1_0.xml b/data/etc/android.software.xr.api.openxr-1_0.xml
new file mode 100644
index 0000000..71c4a94
--- /dev/null
+++ b/data/etc/android.software.xr.api.openxr-1_0.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature to indicate the device has a runtime
+     that supports OpenXR 1.0 (0x00010000). -->
+<permissions>
+    <feature name="android.software.xr.api.openxr" version="65536" />
+</permissions>
diff --git a/data/etc/android.software.xr.api.openxr-1_1.xml b/data/etc/android.software.xr.api.openxr-1_1.xml
new file mode 100644
index 0000000..45c1065
--- /dev/null
+++ b/data/etc/android.software.xr.api.openxr-1_1.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature to indicate the device has a runtime
+     that supports OpenXR 1.1 (0x00010001). -->
+<permissions>
+    <feature name="android.software.xr.api.openxr" version="65537" />
+</permissions>
diff --git a/data/etc/android.software.xr.api.openxr-1_2.xml b/data/etc/android.software.xr.api.openxr-1_2.xml
new file mode 100644
index 0000000..ba11b8d
--- /dev/null
+++ b/data/etc/android.software.xr.api.openxr-1_2.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature to indicate the device has a runtime
+     that supports OpenXR 1.2 (0x00010002). -->
+<permissions>
+    <feature name="android.software.xr.api.openxr" version="65538" />
+</permissions>
diff --git a/data/etc/android.software.xr.api.spatial-1.xml b/data/etc/android.software.xr.api.spatial-1.xml
new file mode 100644
index 0000000..ce425aa
--- /dev/null
+++ b/data/etc/android.software.xr.api.spatial-1.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature to indicate the device has a runtime
+     that supports Android XR Spatial APIs, version 1. -->
+<permissions>
+    <feature name="android.software.xr.api.spatial" version="1" />
+</permissions>
diff --git a/data/etc/go_handheld_core_hardware.xml b/data/etc/go_handheld_core_hardware.xml
index 8df7fdb..a092842 100644
--- a/data/etc/go_handheld_core_hardware.xml
+++ b/data/etc/go_handheld_core_hardware.xml
@@ -51,6 +51,9 @@
     <!-- Feature to specify if the device supports adding device admins. -->
     <feature name="android.software.device_admin" />
 
+    <!-- Feature to specify if the device support managed users. -->
+    <feature name="android.software.managed_users" />
+
     <!-- Devices with all optimizations required to support VR Mode and
          pass all CDD requirements for this feature may include
          android.hardware.vr.high_performance -->
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
index 228347d..2e346bb 100644
--- a/include/input/InputConsumerNoResampling.h
+++ b/include/input/InputConsumerNoResampling.h
@@ -211,16 +211,17 @@
      * `consumeBatchedInputEvents`.
      */
     std::map<DeviceId, std::queue<InputMessage>> mBatches;
+
     /**
-     * Creates a MotionEvent by consuming samples from the provided queue. If one message has
-     * eventTime > adjustedFrameTime, all subsequent messages in the queue will be skipped. It is
-     * assumed that messages are queued in chronological order. In other words, only events that
-     * occurred prior to the adjustedFrameTime will be consumed.
-     * @param requestedFrameTime the time up to which to consume events.
-     * @param messages the queue of messages to consume from
+     * Creates a MotionEvent by consuming samples from the provided queue. Consumes all messages
+     * with eventTime <= requestedFrameTime - resampleLatency, where `resampleLatency` is latency
+     * introduced by the resampler. Assumes that messages are queued in chronological order.
+     * @param requestedFrameTime The time up to which consume messages, as given by the inequality
+     * above. If std::nullopt, everything in messages will be consumed.
+     * @param messages the queue of messages to consume from.
      */
     std::pair<std::unique_ptr<MotionEvent>, std::optional<uint32_t>> createBatchedMotionEvent(
-            const nsecs_t requestedFrameTime, std::queue<InputMessage>& messages);
+            const std::optional<nsecs_t> requestedFrameTime, std::queue<InputMessage>& messages);
 
     /**
      * Consumes the batched input events, optionally allowing the caller to specify a device id
diff --git a/include/input/Resampler.h b/include/input/Resampler.h
index 47519c2..6d95ca7 100644
--- a/include/input/Resampler.h
+++ b/include/input/Resampler.h
@@ -16,10 +16,14 @@
 
 #pragma once
 
+#include <array>
 #include <chrono>
+#include <iterator>
 #include <optional>
 #include <vector>
 
+#include <android-base/logging.h>
+#include <ftl/mixins.h>
 #include <input/Input.h>
 #include <input/InputTransport.h>
 #include <input/RingBuffer.h>
@@ -79,13 +83,127 @@
         PointerCoords coords;
     };
 
+    /**
+     * Container that stores pointers as an associative array, supporting O(1) lookup by pointer id,
+     * as well as forward iteration in the order in which the pointer or pointers were inserted in
+     * the container. PointerMap has a maximum capacity equal to MAX_POINTERS.
+     */
+    class PointerMap {
+    public:
+        struct PointerId : ftl::DefaultConstructible<PointerId, int32_t>,
+                           ftl::Equatable<PointerId> {
+            using DefaultConstructible::DefaultConstructible;
+        };
+
+        /**
+         * Custom iterator to enable use of range-based for loops.
+         */
+        template <typename T>
+        class iterator {
+        public:
+            using iterator_category = std::forward_iterator_tag;
+            using value_type = T;
+            using difference_type = std::ptrdiff_t;
+            using pointer = T*;
+            using reference = T&;
+
+            explicit iterator(pointer element) : mElement{element} {}
+
+            friend bool operator==(const iterator& lhs, const iterator& rhs) {
+                return lhs.mElement == rhs.mElement;
+            }
+
+            friend bool operator!=(const iterator& lhs, const iterator& rhs) {
+                return !(lhs == rhs);
+            }
+
+            iterator operator++() {
+                ++mElement;
+                return *this;
+            }
+
+            reference operator*() const { return *mElement; }
+
+        private:
+            pointer mElement;
+        };
+
+        PointerMap() {
+            idToIndex.fill(std::nullopt);
+            for (Pointer& pointer : pointers) {
+                pointer.properties.clear();
+                pointer.coords.clear();
+            }
+        }
+
+        /**
+         * Forward iterators to traverse the pointers in `pointers`. The order of the pointers is
+         * determined by the order in which they were inserted (not by id).
+         */
+        iterator<Pointer> begin() { return iterator<Pointer>{&pointers[0]}; }
+
+        iterator<const Pointer> begin() const { return iterator<const Pointer>{&pointers[0]}; }
+
+        iterator<Pointer> end() { return iterator<Pointer>{&pointers[nextPointerIndex]}; }
+
+        iterator<const Pointer> end() const {
+            return iterator<const Pointer>{&pointers[nextPointerIndex]};
+        }
+
+        /**
+         * Inserts the given pointer into the PointerMap. Precondition: The current number of
+         * contained pointers must be less than MAX_POINTERS when this function is called. It
+         * fatally logs if the user tries to insert more than MAX_POINTERS, or if pointer id is out
+         * of bounds.
+         */
+        void insert(const Pointer& pointer) {
+            LOG_IF(FATAL, nextPointerIndex >= pointers.size())
+                    << "Cannot insert more than " << MAX_POINTERS << " in PointerMap.";
+            LOG_IF(FATAL, (pointer.properties.id < 0) || (pointer.properties.id > MAX_POINTER_ID))
+                    << "Invalid pointer id.";
+            idToIndex[pointer.properties.id] = std::optional<size_t>{nextPointerIndex};
+            pointers[nextPointerIndex] = pointer;
+            ++nextPointerIndex;
+        }
+
+        /**
+         * Returns the pointer associated with the provided id if it exists.
+         * Otherwise, std::nullopt is returned.
+         */
+        std::optional<Pointer> find(PointerId id) const {
+            const int32_t idValue = ftl::to_underlying(id);
+            LOG_IF(FATAL, (idValue < 0) || (idValue > MAX_POINTER_ID)) << "Invalid pointer id.";
+            const std::optional<size_t> index = idToIndex[idValue];
+            return index.has_value() ? std::optional{pointers[*index]} : std::nullopt;
+        }
+
+    private:
+        /**
+         * The index at which a pointer is inserted in `pointers`. Likewise, it represents the
+         * number of pointers in PointerMap.
+         */
+        size_t nextPointerIndex{0};
+
+        /**
+         * Sequentially stores pointers. Each pointer's position is determined by the value of
+         * nextPointerIndex at insertion time.
+         */
+        std::array<Pointer, MAX_POINTERS + 1> pointers;
+
+        /**
+         * Maps each pointer id to its associated index in pointers. If no pointer with the id
+         * exists in pointers, the mapped value is std::nullopt.
+         */
+        std::array<std::optional<size_t>, MAX_POINTER_ID + 1> idToIndex;
+    };
+
     struct Sample {
         std::chrono::nanoseconds eventTime;
-        std::vector<Pointer> pointers;
+        PointerMap pointerMap;
 
         std::vector<PointerCoords> asPointerCoords() const {
             std::vector<PointerCoords> pointersCoords;
-            for (const Pointer& pointer : pointers) {
+            for (const Pointer& pointer : pointerMap) {
                 pointersCoords.push_back(pointer.coords);
             }
             return pointersCoords;
@@ -100,13 +218,12 @@
     RingBuffer<Sample> mLatestSamples{/*capacity=*/2};
 
     /**
-     * Latest sample in mLatestSamples after resampling motion event. Used to compare if a pointer
-     * does not move between samples.
+     * Latest sample in mLatestSamples after resampling motion event.
      */
     std::optional<Sample> mLastRealSample;
 
     /**
-     * Latest prediction. Used to overwrite motion event samples if a set of conditions is met.
+     * Latest prediction. That is, the latest extrapolated sample.
      */
     std::optional<Sample> mPreviousPrediction;
 
@@ -134,12 +251,12 @@
     bool canInterpolate(const InputMessage& futureSample) const;
 
     /**
-     * Returns a sample interpolated between the latest sample of mLatestSamples and futureSample,
+     * Returns a sample interpolated between the latest sample of mLatestSamples and futureMessage,
      * if the conditions from canInterpolate are satisfied. Otherwise, returns nullopt.
      * mLatestSamples must have at least one sample when attemptInterpolation is called.
      */
     std::optional<Sample> attemptInterpolation(std::chrono::nanoseconds resampleTime,
-                                               const InputMessage& futureSample) const;
+                                               const InputMessage& futureMessage) const;
 
     /**
      * Checks if there are necessary conditions to extrapolate. That is, there are at least two
@@ -156,7 +273,8 @@
     std::optional<Sample> attemptExtrapolation(std::chrono::nanoseconds resampleTime) const;
 
     /**
-     * Iterates through motion event samples, and calls overwriteStillPointers on each sample.
+     * Iterates through motion event samples, and replaces real coordinates with resampled
+     * coordinates to avoid jerkiness in certain conditions.
      */
     void overwriteMotionEventSamples(MotionEvent& motionEvent) const;
 
@@ -174,4 +292,5 @@
 
     inline static void addSampleToMotionEvent(const Sample& sample, MotionEvent& motionEvent);
 };
+
 } // namespace android
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 6903cb5..2ef642a 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -871,6 +871,10 @@
         symbol_file: "libbinder_rpc_unstable.map.txt",
     },
 
+    header_abi_checker: {
+        enabled: false,
+    },
+
     // This library is intentionally limited to these targets, and it will be removed later.
     // Do not expand the visibility.
     visibility: [
diff --git a/libs/binder/BackendUnifiedServiceManager.cpp b/libs/binder/BackendUnifiedServiceManager.cpp
index f7b9f05..d32eecd 100644
--- a/libs/binder/BackendUnifiedServiceManager.cpp
+++ b/libs/binder/BackendUnifiedServiceManager.cpp
@@ -105,7 +105,8 @@
 };
 
 bool BinderCacheWithInvalidation::isClientSideCachingEnabled(const std::string& serviceName) {
-    if (ProcessState::self()->getThreadPoolMaxTotalThreadCount() <= 0) {
+    sp<ProcessState> self = ProcessState::selfOrNull();
+    if (!self || self->getThreadPoolMaxTotalThreadCount() <= 0) {
         ALOGW("Thread Pool max thread count is 0. Cannot cache binder as linkToDeath cannot be "
               "implemented. serviceName: %s",
               serviceName.c_str());
@@ -172,10 +173,6 @@
     mCacheForGetService = std::make_shared<BinderCacheWithInvalidation>();
 }
 
-sp<AidlServiceManager> BackendUnifiedServiceManager::getImpl() {
-    return mTheRealServiceManager;
-}
-
 Status BackendUnifiedServiceManager::getService(const ::std::string& name,
                                                 sp<IBinder>* _aidl_return) {
     os::Service service;
diff --git a/libs/binder/BackendUnifiedServiceManager.h b/libs/binder/BackendUnifiedServiceManager.h
index feb8470..abc0eda 100644
--- a/libs/binder/BackendUnifiedServiceManager.h
+++ b/libs/binder/BackendUnifiedServiceManager.h
@@ -121,7 +121,6 @@
 public:
     explicit BackendUnifiedServiceManager(const sp<os::IServiceManager>& impl);
 
-    sp<os::IServiceManager> getImpl();
     binder::Status getService(const ::std::string& name, sp<IBinder>* _aidl_return) override;
     binder::Status getService2(const ::std::string& name, os::Service* out) override;
     binder::Status checkService(const ::std::string& name, os::Service* out) override;
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 32388db..39d8c24 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -561,8 +561,9 @@
     sp<IBinder> svc = checkService(name);
     if (svc != nullptr) return svc;
 
+    sp<ProcessState> self = ProcessState::selfOrNull();
     const bool isVendorService =
-        strcmp(ProcessState::self()->getDriverName().c_str(), "/dev/vndbinder") == 0;
+            self && strcmp(self->getDriverName().c_str(), "/dev/vndbinder") == 0;
     constexpr auto timeout = 5s;
     const auto startTime = std::chrono::steady_clock::now();
     // Vendor code can't access system properties
@@ -579,7 +580,7 @@
     const useconds_t sleepTime = gSystemBootCompleted ? 1000 : 100;
 
     ALOGI("Waiting for service '%s' on '%s'...", String8(name).c_str(),
-          ProcessState::self()->getDriverName().c_str());
+          self ? self->getDriverName().c_str() : "RPC accessors only");
 
     int n = 0;
     while (std::chrono::steady_clock::now() - startTime < timeout) {
@@ -661,7 +662,8 @@
     if (Status status = realGetService(name, &out); !status.isOk()) {
         ALOGW("Failed to getService in waitForService for %s: %s", name.c_str(),
               status.toString8().c_str());
-        if (0 == ProcessState::self()->getThreadPoolMaxTotalThreadCount()) {
+        sp<ProcessState> self = ProcessState::selfOrNull();
+        if (self && 0 == self->getThreadPoolMaxTotalThreadCount()) {
             ALOGW("Got service, but may be racey because we could not wait efficiently for it. "
                   "Threadpool has 0 guaranteed threads. "
                   "Is the threadpool configured properly? "
@@ -695,9 +697,10 @@
             if (waiter->mBinder != nullptr) return waiter->mBinder;
         }
 
+        sp<ProcessState> self = ProcessState::selfOrNull();
         ALOGW("Waited one second for %s (is service started? Number of threads started in the "
               "threadpool: %zu. Are binder threads started and available?)",
-              name.c_str(), ProcessState::self()->getThreadPoolMaxTotalThreadCount());
+              name.c_str(), self ? self->getThreadPoolMaxTotalThreadCount() : 0);
 
         // Handle race condition for lazy services. Here is what can happen:
         // - the service dies (not processed by init yet).
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index 020ebcc..8404a48 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -139,6 +139,9 @@
         "--raw-line",
         "use libc::sockaddr;",
     ],
+    cflags: [
+        "-DANDROID_PLATFORM",
+    ],
     shared_libs: [
         "libbinder_ndk",
     ],
@@ -179,6 +182,9 @@
         // rustified
         "libbinder_ndk_bindgen_flags.txt",
     ],
+    cflags: [
+        "-DANDROID_PLATFORM",
+    ],
     shared_libs: [
         "libbinder_ndk_on_trusty_mock",
         "libc++",
diff --git a/libs/binder/rust/Cargo.toml b/libs/binder/rust/Cargo.toml
new file mode 100644
index 0000000..e5738c5
--- /dev/null
+++ b/libs/binder/rust/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "android-binder"
+version = "0.1.0"
+edition = "2021"
+description = "Safe bindings to Android Binder, restricted to the NDK"
+license = "Apache-2.0"
+
+[dependencies]
+binder-ndk-sys = { package = "android-binder-ndk-sys", version = "0.1", path = "./sys" }
+downcast-rs = "1.2.1"
+libc = "0.2.159"
+
+[lints.rust.unexpected_cfgs]
+level = "warn"
+check-cfg = ["cfg(android_vendor)", "cfg(android_ndk)", "cfg(android_vndk)", "cfg(trusty)"]
diff --git a/libs/binder/rust/build.rs b/libs/binder/rust/build.rs
new file mode 100644
index 0000000..f3e6b53
--- /dev/null
+++ b/libs/binder/rust/build.rs
@@ -0,0 +1,4 @@
+fn main() {
+    // Anything with cargo is NDK only. If you want to access anything else, use Soong.
+    println!("cargo::rustc-cfg=android_ndk");
+}
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 23026e5..8c0501b 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -207,8 +207,10 @@
 /// Corresponds to TF_ONE_WAY -- an asynchronous call.
 pub const FLAG_ONEWAY: TransactionFlags = sys::FLAG_ONEWAY;
 /// Corresponds to TF_CLEAR_BUF -- clear transaction buffers after call is made.
+#[cfg(not(android_ndk))]
 pub const FLAG_CLEAR_BUF: TransactionFlags = sys::FLAG_CLEAR_BUF;
 /// Set to the vendor flag if we are building for the VNDK, 0 otherwise
+#[cfg(not(android_ndk))]
 pub const FLAG_PRIVATE_LOCAL: TransactionFlags = sys::FLAG_PRIVATE_LOCAL;
 
 /// Internal interface of binder local or remote objects for making
@@ -221,7 +223,7 @@
     fn is_binder_alive(&self) -> bool;
 
     /// Indicate that the service intends to receive caller security contexts.
-    #[cfg(not(android_vndk))]
+    #[cfg(not(any(android_vndk, android_ndk)))]
     fn set_requesting_sid(&mut self, enable: bool);
 
     /// Dump this object to the given file handle
@@ -346,7 +348,6 @@
                 panic!("Expected non-null class pointer from AIBinder_Class_define!");
             }
             sys::AIBinder_Class_setOnDump(class, Some(I::on_dump));
-            sys::AIBinder_Class_setHandleShellCommand(class, None);
             class
         };
         InterfaceClass(ptr)
@@ -714,7 +715,7 @@
 pub struct BinderFeatures {
     /// Indicates that the service intends to receive caller security contexts. This must be true
     /// for `ThreadState::with_calling_sid` to work.
-    #[cfg(not(android_vndk))]
+    #[cfg(not(any(android_vndk, android_ndk)))]
     pub set_requesting_sid: bool,
     // Ensure that clients include a ..BinderFeatures::default() to preserve backwards compatibility
     // when new fields are added. #[non_exhaustive] doesn't work because it prevents struct
@@ -916,8 +917,12 @@
         impl $native {
             /// Create a new binder service.
             pub fn new_binder<T: $interface + Sync + Send + 'static>(inner: T, features: $crate::BinderFeatures) -> $crate::Strong<dyn $interface> {
+                #[cfg(not(android_ndk))]
                 let mut binder = $crate::binder_impl::Binder::new_with_stability($native(Box::new(inner)), $stability);
-                #[cfg(not(android_vndk))]
+                #[cfg(android_ndk)]
+                let mut binder = $crate::binder_impl::Binder::new($native(Box::new(inner)));
+
+                #[cfg(not(any(android_vndk, android_ndk)))]
                 $crate::binder_impl::IBinderInternal::set_requesting_sid(&mut binder, features.set_requesting_sid);
                 $crate::Strong::new(Box::new(binder))
             }
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index f7f3f35..14493db 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -100,11 +100,11 @@
 mod native;
 mod parcel;
 mod proxy;
-#[cfg(not(trusty))]
+#[cfg(not(any(trusty, android_ndk)))]
 mod service;
-#[cfg(not(trusty))]
+#[cfg(not(any(trusty, android_ndk)))]
 mod state;
-#[cfg(not(any(android_vendor, android_vndk)))]
+#[cfg(not(any(android_vendor, android_ndk, android_vndk)))]
 mod system_only;
 
 use binder_ndk_sys as sys;
@@ -114,15 +114,18 @@
 pub use error::{ExceptionCode, IntoBinderResult, Status, StatusCode};
 pub use parcel::{ParcelFileDescriptor, Parcelable, ParcelableHolder};
 pub use proxy::{DeathRecipient, SpIBinder, WpIBinder};
-#[cfg(not(trusty))]
+#[cfg(not(any(trusty, android_ndk)))]
 pub use service::{
     add_service, check_interface, check_service, force_lazy_services_persist,
-    get_declared_instances, get_interface, get_service, is_declared, is_handling_transaction,
-    register_lazy_service, wait_for_interface, wait_for_service, LazyServiceGuard,
+    get_declared_instances, is_declared, is_handling_transaction, register_lazy_service,
+    wait_for_interface, wait_for_service, LazyServiceGuard,
 };
-#[cfg(not(trusty))]
+#[cfg(not(any(trusty, android_ndk)))]
+#[allow(deprecated)]
+pub use service::{get_interface, get_service};
+#[cfg(not(any(trusty, android_ndk)))]
 pub use state::{ProcessState, ThreadState};
-#[cfg(not(any(android_vendor, android_vndk)))]
+#[cfg(not(any(android_vendor, android_vndk, android_ndk)))]
 pub use system_only::{delegate_accessor, Accessor, ConnectionInfo};
 
 /// Binder result containing a [`Status`] on error.
@@ -134,9 +137,10 @@
     pub use crate::binder::{
         IBinderInternal, InterfaceClass, LocalStabilityType, Remotable, Stability, StabilityType,
         ToAsyncInterface, ToSyncInterface, TransactionCode, TransactionFlags, VintfStabilityType,
-        FIRST_CALL_TRANSACTION, FLAG_CLEAR_BUF, FLAG_ONEWAY, FLAG_PRIVATE_LOCAL,
-        LAST_CALL_TRANSACTION,
+        FIRST_CALL_TRANSACTION, FLAG_ONEWAY, LAST_CALL_TRANSACTION,
     };
+    #[cfg(not(android_ndk))]
+    pub use crate::binder::{FLAG_CLEAR_BUF, FLAG_PRIVATE_LOCAL};
     pub use crate::binder_async::BinderAsyncRuntime;
     pub use crate::error::status_t;
     pub use crate::native::Binder;
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index c87cc94..9e1cfd6 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-use crate::binder::{
-    AsNative, Interface, InterfaceClassMethods, Remotable, Stability, TransactionCode,
-};
+#[cfg(not(android_ndk))]
+use crate::binder::Stability;
+use crate::binder::{AsNative, Interface, InterfaceClassMethods, Remotable, TransactionCode};
 use crate::error::{status_result, status_t, Result, StatusCode};
 use crate::parcel::{BorrowedParcel, Serialize};
 use crate::proxy::SpIBinder;
@@ -76,14 +76,32 @@
     /// This moves the `rust_object` into an owned [`Box`] and Binder will
     /// manage its lifetime.
     pub fn new(rust_object: T) -> Binder<T> {
-        Self::new_with_stability(rust_object, Stability::default())
+        #[cfg(not(android_ndk))]
+        {
+            Self::new_with_stability(rust_object, Stability::default())
+        }
+        #[cfg(android_ndk)]
+        {
+            Self::new_unmarked(rust_object)
+        }
     }
 
     /// Create a new Binder remotable object with the given stability
     ///
     /// This moves the `rust_object` into an owned [`Box`] and Binder will
     /// manage its lifetime.
+    #[cfg(not(android_ndk))]
     pub fn new_with_stability(rust_object: T, stability: Stability) -> Binder<T> {
+        let mut binder = Self::new_unmarked(rust_object);
+        binder.mark_stability(stability);
+        binder
+    }
+
+    /// Creates a new Binder remotable object with unset stability
+    ///
+    /// This is internal because normally we want to set the stability explicitly,
+    /// however for the NDK variant we cannot mark the stability.
+    fn new_unmarked(rust_object: T) -> Binder<T> {
         let class = T::get_class();
         let rust_object = Box::into_raw(Box::new(rust_object));
         // Safety: `AIBinder_new` expects a valid class pointer (which we
@@ -93,9 +111,7 @@
         // decremented via `AIBinder_decStrong` when the reference lifetime
         // ends.
         let ibinder = unsafe { sys::AIBinder_new(class.into(), rust_object as *mut c_void) };
-        let mut binder = Binder { ibinder, rust_object };
-        binder.mark_stability(stability);
-        binder
+        Binder { ibinder, rust_object }
     }
 
     /// Set the extension of a binder interface. This allows a downstream
@@ -189,6 +205,7 @@
     }
 
     /// Mark this binder object with the given stability guarantee
+    #[cfg(not(android_ndk))]
     fn mark_stability(&mut self, stability: Stability) {
         match stability {
             Stability::Local => self.mark_local_stability(),
@@ -215,7 +232,7 @@
 
     /// Mark this binder object with local stability, which is vendor if we are
     /// building for android_vendor and system otherwise.
-    #[cfg(not(android_vendor))]
+    #[cfg(not(any(android_vendor, android_ndk)))]
     fn mark_local_stability(&mut self) {
         // Safety: Self always contains a valid `AIBinder` pointer, so we can
         // always call this C API safely.
diff --git a/libs/binder/rust/src/parcel.rs b/libs/binder/rust/src/parcel.rs
index 3bfc425..485b0bd 100644
--- a/libs/binder/rust/src/parcel.rs
+++ b/libs/binder/rust/src/parcel.rs
@@ -197,6 +197,7 @@
 // Data serialization methods
 impl<'a> BorrowedParcel<'a> {
     /// Data written to parcelable is zero'd before being deleted or reallocated.
+    #[cfg(not(android_ndk))]
     pub fn mark_sensitive(&mut self) {
         // Safety: guaranteed to have a parcel object, and this method never fails
         unsafe { sys::AParcel_markSensitive(self.as_native()) }
@@ -342,6 +343,7 @@
 
 impl Parcel {
     /// Data written to parcelable is zero'd before being deleted or reallocated.
+    #[cfg(not(android_ndk))]
     pub fn mark_sensitive(&mut self) {
         self.borrowed().mark_sensitive()
     }
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index 04f1517..593d12c 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -298,7 +298,7 @@
         unsafe { sys::AIBinder_isAlive(self.as_native()) }
     }
 
-    #[cfg(not(android_vndk))]
+    #[cfg(not(any(android_vndk, android_ndk)))]
     fn set_requesting_sid(&mut self, enable: bool) {
         // Safety: `SpIBinder` guarantees that `self` always contains a valid
         // pointer to an `AIBinder`.
diff --git a/libs/binder/rust/src/service.rs b/libs/binder/rust/src/service.rs
index 29dd8e1..f4fdcf5 100644
--- a/libs/binder/rust/src/service.rs
+++ b/libs/binder/rust/src/service.rs
@@ -176,6 +176,7 @@
 /// seconds if it doesn't yet exist.
 #[deprecated = "this polls 5s, use wait_for_interface or check_interface"]
 pub fn get_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
+    #[allow(deprecated)]
     interface_cast(get_service(name))
 }
 
diff --git a/libs/binder/rust/sys/BinderBindings.hpp b/libs/binder/rust/sys/BinderBindings.hpp
index bd666fe..557f0e8 100644
--- a/libs/binder/rust/sys/BinderBindings.hpp
+++ b/libs/binder/rust/sys/BinderBindings.hpp
@@ -15,15 +15,19 @@
  */
 
 #include <android/binder_ibinder.h>
+#include <android/binder_parcel.h>
+#include <android/binder_status.h>
+
+/* Platform only */
+#if defined(ANDROID_PLATFORM) || defined(__ANDROID_VENDOR__)
 #include <android/binder_ibinder_platform.h>
 #include <android/binder_manager.h>
-#include <android/binder_parcel.h>
 #include <android/binder_parcel_platform.h>
 #include <android/binder_process.h>
 #include <android/binder_rpc.h>
 #include <android/binder_shell.h>
 #include <android/binder_stability.h>
-#include <android/binder_status.h>
+#endif
 
 namespace android {
 
@@ -81,8 +85,10 @@
 
 enum {
     FLAG_ONEWAY = FLAG_ONEWAY,
+#if defined(ANDROID_PLATFORM) || defined(__ANDROID_VENDOR__)
     FLAG_CLEAR_BUF = FLAG_CLEAR_BUF,
     FLAG_PRIVATE_LOCAL = FLAG_PRIVATE_LOCAL,
+#endif
 };
 
 } // namespace consts
diff --git a/libs/binder/rust/sys/Cargo.toml b/libs/binder/rust/sys/Cargo.toml
new file mode 100644
index 0000000..ad8e9c2
--- /dev/null
+++ b/libs/binder/rust/sys/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "android-binder-ndk-sys"
+version = "0.1.0"
+edition = "2021"
+description = "Bindgen bindings to android binder, restricted to the NDK"
+license = "Apache-2.0"
+
+[dependencies]
+
+[lib]
+path = "lib.rs"
+
+[build-dependencies]
+bindgen = "0.70.1"
diff --git a/libs/binder/rust/sys/build.rs b/libs/binder/rust/sys/build.rs
new file mode 100644
index 0000000..cb9c65b
--- /dev/null
+++ b/libs/binder/rust/sys/build.rs
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use std::env;
+use std::path::PathBuf;
+
+fn main() {
+    let ndk_home = PathBuf::from(env::var("ANDROID_NDK_HOME").unwrap());
+    let toolchain = ndk_home.join("toolchains/llvm/prebuilt/linux-x86_64/");
+    let sysroot = toolchain.join("sysroot");
+    let bindings = bindgen::Builder::default()
+        .clang_arg(format!("--sysroot={}", sysroot.display()))
+        // TODO figure out what the "standard" #define is and use that instead
+        .header("BinderBindings.hpp")
+        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
+        // Keep in sync with libbinder_ndk_bindgen_flags.txt
+        .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: true })
+        .constified_enum("android::c_interface::consts::.*")
+        .allowlist_type("android::c_interface::.*")
+        .allowlist_type("AStatus")
+        .allowlist_type("AIBinder_Class")
+        .allowlist_type("AIBinder")
+        .allowlist_type("AIBinder_Weak")
+        .allowlist_type("AIBinder_DeathRecipient")
+        .allowlist_type("AParcel")
+        .allowlist_type("binder_status_t")
+        .blocklist_function("vprintf")
+        .blocklist_function("strtold")
+        .blocklist_function("_vtlog")
+        .blocklist_function("vscanf")
+        .blocklist_function("vfprintf_worker")
+        .blocklist_function("vsprintf")
+        .blocklist_function("vsnprintf")
+        .blocklist_function("vsnprintf_filtered")
+        .blocklist_function("vfscanf")
+        .blocklist_function("vsscanf")
+        .blocklist_function("vdprintf")
+        .blocklist_function("vasprintf")
+        .blocklist_function("strtold_l")
+        .allowlist_function(".*")
+        .generate()
+        .expect("Couldn't generate bindings");
+    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+    bindings.write_to_file(out_path.join("bindings.rs")).expect("Couldn't write bindings.");
+    println!("cargo::rustc-link-lib=binder_ndk");
+}
diff --git a/libs/binder/rust/sys/lib.rs b/libs/binder/rust/sys/lib.rs
index 5352473..349e5a9 100644
--- a/libs/binder/rust/sys/lib.rs
+++ b/libs/binder/rust/sys/lib.rs
@@ -20,6 +20,7 @@
 use std::fmt;
 
 #[cfg(not(target_os = "trusty"))]
+#[allow(bad_style)]
 mod bindings {
     include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
 }
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 49f4cba..495418b 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -52,19 +52,18 @@
 namespace {
 
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
-// RAII wrapper to defer arbitrary work until the Deferred instance is deleted.
-template <class F>
-class Deferred {
+template <class Mutex>
+class UnlockGuard {
 public:
-    explicit Deferred(F f) : mF{std::move(f)} {}
+    explicit UnlockGuard(Mutex& lock) : mLock{lock} { mLock.unlock(); }
 
-    ~Deferred() { mF(); }
+    ~UnlockGuard() { mLock.lock(); }
 
-    Deferred(const Deferred&) = delete;
-    Deferred& operator=(const Deferred&) = delete;
+    UnlockGuard(const UnlockGuard&) = delete;
+    UnlockGuard& operator=(const UnlockGuard&) = delete;
 
 private:
-    F mF;
+    Mutex& mLock;
 };
 #endif
 
@@ -271,9 +270,6 @@
 void BLASTBufferQueue::onFirstRef() {
     // safe default, most producers are expected to override this
     mProducer->setMaxDequeuedBufferCount(2);
-#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
-    mBufferReleaseThread.emplace(sp<BLASTBufferQueue>::fromExisting(this));
-#endif
 }
 
 void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height,
@@ -297,11 +293,11 @@
     mSurfaceControl = surface;
     SurfaceComposerClient::Transaction t;
     if (surfaceControlChanged) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+        updateBufferReleaseProducer();
+#endif
         t.setFlags(mSurfaceControl, layer_state_t::eEnableBackpressure,
                    layer_state_t::eEnableBackpressure);
-#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
-        t.setBufferReleaseChannel(mSurfaceControl, mBufferReleaseProducer);
-#endif
         applyTransaction = true;
     }
     mTransformHint = mSurfaceControl->getTransformHint();
@@ -325,7 +321,7 @@
     }
     if (applyTransaction) {
         // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
-        t.setApplyToken(mApplyToken).apply(false, true);
+        t.setApplyToken(mApplyToken).apply(false /* synchronous */, true /* oneWay */);
     }
 }
 
@@ -419,7 +415,6 @@
                                                     stat.latchTime,
                                                     stat.frameEventStats.dequeueReadyTime);
                 }
-#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
                 auto currFrameNumber = stat.frameEventStats.frameNumber;
                 std::vector<ReleaseCallbackId> staleReleases;
                 for (const auto& [key, value]: mSubmitted) {
@@ -435,7 +430,6 @@
                                                 stat.currentMaxAcquiredBufferCount,
                                                 true /* fakeRelease */);
                 }
-#endif
             } else {
                 BQA_LOGE("Failed to find matching SurfaceControl in transactionCallback");
             }
@@ -469,6 +463,9 @@
             return;
         }
         bbq->releaseBufferCallback(id, releaseFence, currentMaxAcquiredBufferCount);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+        bbq->drainBufferReleaseConsumer();
+#endif
     };
 }
 
@@ -535,8 +532,6 @@
                                      const sp<Fence>& releaseFence) {
     auto it = mSubmitted.find(callbackId);
     if (it == mSubmitted.end()) {
-        BQA_LOGE("ERROR: releaseBufferCallback without corresponding submitted buffer %s",
-                 callbackId.to_string().c_str());
         return;
     }
     mNumAcquired--;
@@ -646,12 +641,7 @@
                            bufferItem.mGraphicBuffer->getHeight(), bufferItem.mTransform,
                            bufferItem.mScalingMode, crop);
 
-#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
-    ReleaseBufferCallback releaseBufferCallback =
-            applyTransaction ? nullptr : makeReleaseBufferCallbackThunk();
-#else
     auto releaseBufferCallback = makeReleaseBufferCallbackThunk();
-#endif
     sp<Fence> fence = bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE;
 
     nsecs_t dequeueTime = -1;
@@ -1230,12 +1220,7 @@
         // we want to ignore it. This must be done before unlocking the BufferQueue lock to ensure
         // we don't miss an interrupt.
         bbq->mBufferReleaseReader->clearInterrupts();
-        bbq->mThreadsBlockingOnDequeue++;
-        bufferQueueLock.unlock();
-        Deferred cleanup{[&]() {
-            bufferQueueLock.lock();
-            bbq->mThreadsBlockingOnDequeue--;
-        }};
+        UnlockGuard unlockGuard{bufferQueueLock};
 
         ATRACE_FORMAT("waiting for free buffer");
         ReleaseCallbackId id;
@@ -1345,6 +1330,35 @@
 
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
 
+void BLASTBufferQueue::updateBufferReleaseProducer() {
+    // SELinux policy may prevent this process from sending the BufferReleaseChannel's file
+    // descriptor to SurfaceFlinger, causing the entire transaction to be dropped. We send this
+    // transaction independently of any other updates to ensure those updates aren't lost.
+    SurfaceComposerClient::Transaction t;
+    status_t status = t.setApplyToken(mApplyToken)
+                              .setBufferReleaseChannel(mSurfaceControl, mBufferReleaseProducer)
+                              .apply(false /* synchronous */, true /* oneWay */);
+    if (status != OK) {
+        ALOGW("[%s] %s - failed to set buffer release channel on %s", mName.c_str(),
+              statusToString(status).c_str(), mSurfaceControl->getName().c_str());
+    }
+}
+
+void BLASTBufferQueue::drainBufferReleaseConsumer() {
+    ATRACE_CALL();
+    while (true) {
+        ReleaseCallbackId id;
+        sp<Fence> fence;
+        uint32_t maxAcquiredBufferCount;
+        status_t status =
+                mBufferReleaseConsumer->readReleaseFence(id, fence, maxAcquiredBufferCount);
+        if (status != OK) {
+            return;
+        }
+        releaseBufferCallback(id, fence, maxAcquiredBufferCount);
+    }
+}
+
 BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader(BLASTBufferQueue& bbq) : mBbq{bbq} {
     mEpollFd = android::base::unique_fd{epoll_create1(EPOLL_CLOEXEC)};
     LOG_ALWAYS_FATAL_IF(!mEpollFd.ok(),
@@ -1438,95 +1452,6 @@
     }
 }
 
-BLASTBufferQueue::BufferReleaseThread::BufferReleaseThread(const sp<BLASTBufferQueue>& bbq) {
-    android::base::unique_fd epollFd{epoll_create1(EPOLL_CLOEXEC)};
-    LOG_ALWAYS_FATAL_IF(!epollFd.ok(),
-                        "Failed to create buffer release background thread epoll file descriptor. "
-                        "errno=%d message='%s'",
-                        errno, strerror(errno));
-
-    epoll_event registerEndpointFd{};
-    registerEndpointFd.events = EPOLLIN;
-    registerEndpointFd.data.fd = bbq->mBufferReleaseConsumer->getFd();
-    status_t status = epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, bbq->mBufferReleaseConsumer->getFd(),
-                                &registerEndpointFd);
-    LOG_ALWAYS_FATAL_IF(status == -1,
-                        "Failed to register background thread buffer release consumer file "
-                        "descriptor with epoll. errno=%d message='%s'",
-                        errno, strerror(errno));
-
-    // EventFd is used to break the background thread's loop.
-    android::base::unique_fd eventFd{eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)};
-    LOG_ALWAYS_FATAL_IF(!eventFd.ok(),
-                        "Failed to create background thread buffer release event file descriptor. "
-                        "errno=%d message='%s'",
-                        errno, strerror(errno));
-
-    epoll_event registerEventFd{};
-    registerEventFd.events = EPOLLIN;
-    registerEventFd.data.fd = eventFd.get();
-    status = epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, eventFd.get(), &registerEventFd);
-    LOG_ALWAYS_FATAL_IF(status == -1,
-                        "Failed to register background thread event file descriptor with epoll. "
-                        "errno=%d message='%s'",
-                        errno, strerror(errno));
-
-    mEventFd = eventFd.get();
-
-    std::thread([epollFd = std::move(epollFd), eventFd = std::move(eventFd),
-                 weakBbq = wp<BLASTBufferQueue>(bbq)]() {
-        pthread_setname_np(pthread_self(), "BufferReleaseThread");
-        while (true) {
-            epoll_event event{};
-            int eventCount;
-            do {
-                eventCount = epoll_wait(epollFd.get(), &event, 1 /*maxevents*/, -1 /*timeout*/);
-            } while (eventCount == -1 && errno != EINTR);
-
-            if (eventCount == -1) {
-                ALOGE("epoll_wait error while waiting for buffer release in background thread. "
-                      "errno=%d message='%s'",
-                      errno, strerror(errno));
-                continue;
-            }
-
-            // EventFd is used to join this thread.
-            if (event.data.fd == eventFd.get()) {
-                return;
-            }
-
-            sp<BLASTBufferQueue> bbq = weakBbq.promote();
-            if (!bbq) {
-                return;
-            }
-
-            // If there are threads blocking on dequeue, give those threads priority for handling
-            // the release.
-            if (bbq->mThreadsBlockingOnDequeue > 0) {
-                std::this_thread::sleep_for(0ms);
-                continue;
-            }
-
-            ReleaseCallbackId id;
-            sp<Fence> fence;
-            uint32_t maxAcquiredBufferCount;
-            status_t status = bbq->mBufferReleaseConsumer->readReleaseFence(id, fence,
-                                                                            maxAcquiredBufferCount);
-            if (status != OK) {
-                ALOGE("failed to read from buffer release consumer in background thread. errno=%d "
-                      "message='%s'",
-                      errno, strerror(errno));
-                continue;
-            }
-            bbq->releaseBufferCallback(id, fence, maxAcquiredBufferCount);
-        }
-    }).detach();
-}
-
-BLASTBufferQueue::BufferReleaseThread::~BufferReleaseThread() {
-    eventfd_write(mEventFd, 1);
-}
-
 #endif
 
 } // namespace android
diff --git a/libs/gui/BufferReleaseChannel.cpp b/libs/gui/BufferReleaseChannel.cpp
index e9c6ef3..e9cb013 100644
--- a/libs/gui/BufferReleaseChannel.cpp
+++ b/libs/gui/BufferReleaseChannel.cpp
@@ -35,35 +35,35 @@
 namespace {
 
 template <typename T>
-static void readAligned(const void*& buffer, size_t& size, T& value) {
+void readAligned(const void*& buffer, size_t& size, T& value) {
     size -= FlattenableUtils::align<alignof(T)>(buffer);
     FlattenableUtils::read(buffer, size, value);
 }
 
 template <typename T>
-static void writeAligned(void*& buffer, size_t& size, T value) {
+void writeAligned(void*& buffer, size_t& size, T value) {
     size -= FlattenableUtils::align<alignof(T)>(buffer);
     FlattenableUtils::write(buffer, size, value);
 }
 
 template <typename T>
-static void addAligned(size_t& size, T /* value */) {
+void addAligned(size_t& size, T /* value */) {
     size = FlattenableUtils::align<sizeof(T)>(size);
     size += sizeof(T);
 }
 
 template <typename T>
-static inline constexpr uint32_t low32(const T n) {
+inline constexpr uint32_t low32(const T n) {
     return static_cast<uint32_t>(static_cast<uint64_t>(n));
 }
 
 template <typename T>
-static inline constexpr uint32_t high32(const T n) {
+inline constexpr uint32_t high32(const T n) {
     return static_cast<uint32_t>(static_cast<uint64_t>(n) >> 32);
 }
 
 template <typename T>
-static inline constexpr T to64(const uint32_t lo, const uint32_t hi) {
+inline constexpr T to64(const uint32_t lo, const uint32_t hi) {
     return static_cast<T>(static_cast<uint64_t>(hi) << 32 | lo);
 }
 
@@ -139,19 +139,18 @@
     std::lock_guard lock{mMutex};
     Message message;
     mFlattenedBuffer.resize(message.getFlattenedSize());
-    std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer;
+    std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer{};
 
     iovec iov{
             .iov_base = mFlattenedBuffer.data(),
             .iov_len = mFlattenedBuffer.size(),
     };
 
-    msghdr msg{
-            .msg_iov = &iov,
-            .msg_iovlen = 1,
-            .msg_control = controlMessageBuffer.data(),
-            .msg_controllen = controlMessageBuffer.size(),
-    };
+    msghdr msg{};
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+    msg.msg_control = controlMessageBuffer.data();
+    msg.msg_controllen = controlMessageBuffer.size();
 
     ssize_t result;
     do {
@@ -161,7 +160,7 @@
         if (errno == EWOULDBLOCK || errno == EAGAIN) {
             return WOULD_BLOCK;
         }
-        ALOGE("Error reading release fence from socket: error %#x (%s)", errno, strerror(errno));
+        ALOGE("Error reading release fence from socket: error %d (%s)", errno, strerror(errno));
         return UNKNOWN_ERROR;
     }
 
@@ -200,9 +199,9 @@
     return OK;
 }
 
-int BufferReleaseChannel::ProducerEndpoint::writeReleaseFence(const ReleaseCallbackId& callbackId,
-                                                              const sp<Fence>& fence,
-                                                              uint32_t maxAcquiredBufferCount) {
+status_t BufferReleaseChannel::ProducerEndpoint::writeReleaseFence(
+        const ReleaseCallbackId& callbackId, const sp<Fence>& fence,
+        uint32_t maxAcquiredBufferCount) {
     Message message{callbackId, fence ? fence : Fence::NO_FENCE, maxAcquiredBufferCount};
     mFlattenedBuffer.resize(message.getFlattenedSize());
     int flattenedFd;
@@ -213,25 +212,22 @@
         size_t flattenedBufferSize = mFlattenedBuffer.size();
         int* flattenedFdPtr = &flattenedFd;
         size_t flattenedFdCount = 1;
-        if (status_t err = message.flatten(flattenedBufferPtr, flattenedBufferSize, flattenedFdPtr,
-                                           flattenedFdCount);
-            err != OK) {
-            ALOGE("Failed to flatten BufferReleaseChannel message.");
-            return err;
+        if (status_t status = message.flatten(flattenedBufferPtr, flattenedBufferSize,
+                                              flattenedFdPtr, flattenedFdCount);
+            status != OK) {
+            return status;
         }
     }
 
-    iovec iov{
-            .iov_base = mFlattenedBuffer.data(),
-            .iov_len = mFlattenedBuffer.size(),
-    };
+    iovec iov{};
+    iov.iov_base = mFlattenedBuffer.data();
+    iov.iov_len = mFlattenedBuffer.size();
 
-    msghdr msg{
-            .msg_iov = &iov,
-            .msg_iovlen = 1,
-    };
+    msghdr msg{};
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
 
-    std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer;
+    std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer{};
     if (fence && fence->isValid()) {
         msg.msg_control = controlMessageBuffer.data();
         msg.msg_controllen = controlMessageBuffer.size();
@@ -248,7 +244,6 @@
         result = sendmsg(mFd, &msg, 0);
     } while (result == -1 && errno == EINTR);
     if (result == -1) {
-        ALOGD("Error writing release fence to socket: error %#x (%s)", errno, strerror(errno));
         return -errno;
     }
 
@@ -344,13 +339,6 @@
         return -errno;
     }
 
-    // Make the producer write-only
-    if (shutdown(producerFd.get(), SHUT_RD) == -1) {
-        ALOGE("[%s] Failed to shutdown reading on producer socket. errno=%d message='%s'",
-              name.c_str(), errno, strerror(errno));
-        return -errno;
-    }
-
     outConsumer = std::make_unique<ConsumerEndpoint>(name, std::move(consumerFd));
     outProducer = std::make_shared<ProducerEndpoint>(std::move(name), std::move(producerFd));
     return STATUS_OK;
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 422c57b..4b53134 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -664,6 +664,10 @@
         what |= eShadowRadiusChanged;
         shadowRadius = other.shadowRadius;
     }
+    if (other.what & eLutsChanged) {
+        what |= eLutsChanged;
+        luts = other.luts;
+    }
     if (other.what & eDefaultFrameRateCompatibilityChanged) {
         what |= eDefaultFrameRateCompatibilityChanged;
         defaultFrameRateCompatibility = other.defaultFrameRateCompatibility;
@@ -821,6 +825,8 @@
     CHECK_DIFF(diff, eColorSpaceAgnosticChanged, other, colorSpaceAgnostic);
     CHECK_DIFF(diff, eDimmingEnabledChanged, other, dimmingEnabled);
     if (other.what & eBufferReleaseChannelChanged) diff |= eBufferReleaseChannelChanged;
+    if (other.what & eLutsChanged) diff |= eLutsChanged;
+
     return diff;
 }
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index c52e2e1..74097b8 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -29,6 +29,7 @@
 #include <android/gui/IWindowInfosListener.h>
 #include <android/gui/TrustedPresentationThresholds.h>
 #include <android/os/IInputConstants.h>
+#include <gui/DisplayLuts.h>
 #include <gui/FrameRateUtils.h>
 #include <gui/TraceUtils.h>
 #include <utils/Errors.h>
@@ -1347,21 +1348,22 @@
     sp<IBinder> applyToken = mApplyToken ? mApplyToken : getDefaultApplyToken();
 
     sp<ISurfaceComposer> sf(ComposerService::getComposerService());
-    sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken,
-                            mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp,
-                            mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId,
-                            mMergedTransactionIds);
+    status_t binderStatus =
+            sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags,
+                                    applyToken, mInputWindowCommands, mDesiredPresentTime,
+                                    mIsAutoTimestamp, mUncacheBuffers, hasListenerCallbacks,
+                                    listenerCallbacks, mId, mMergedTransactionIds);
     mId = generateId();
 
     // Clear the current states and flags
     clear();
 
-    if (synchronous) {
+    if (synchronous && binderStatus == OK) {
         syncCallback->wait();
     }
 
     mStatus = NO_ERROR;
-    return NO_ERROR;
+    return binderStatus;
 }
 
 sp<IBinder> SurfaceComposerClient::Transaction::sApplyToken = new BBinder();
@@ -1375,7 +1377,7 @@
 
 void SurfaceComposerClient::Transaction::setDefaultApplyToken(sp<IBinder> applyToken) {
     std::scoped_lock lock{sApplyTokenMutex};
-    sApplyToken = applyToken;
+    sApplyToken = std::move(applyToken);
 }
 
 status_t SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction(
@@ -1941,15 +1943,19 @@
 }
 
 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*/) {
+        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)
+
+    s->luts = std::make_shared<gui::DisplayLuts>(base::unique_fd(dup(lutFd.get())), offsets,
+                                                 dimensions, sizes, samplingKeys);
+    s->what |= layer_state_t::eLutsChanged;
+
     registerSurfaceControlForCallback(sc);
     return *this;
 }
diff --git a/libs/gui/aidl/android/gui/Lut.aidl b/libs/gui/aidl/android/gui/Lut.aidl
deleted file mode 100644
index a06e521..0000000
--- a/libs/gui/aidl/android/gui/Lut.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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
index 561e0c0..87b878c 100644
--- a/libs/gui/aidl/android/gui/LutProperties.aidl
+++ b/libs/gui/aidl/android/gui/LutProperties.aidl
@@ -25,7 +25,7 @@
     enum Dimension { ONE_D = 1, THREE_D = 3 }
     Dimension dimension;
 
-    long size;
+    int size;
     @Backing(type="int")
     enum SamplingKey { RGB, MAX_RGB }
     SamplingKey[] samplingKeys;
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 99c64da..8894b66 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -325,6 +325,14 @@
     std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> mBufferReleaseConsumer;
     std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> mBufferReleaseProducer;
 
+    void updateBufferReleaseProducer() REQUIRES(mMutex);
+    void drainBufferReleaseConsumer();
+
+    // BufferReleaseReader is used to do blocking but interruptible reads from the buffer
+    // release channel. To implement this, BufferReleaseReader owns an epoll file descriptor that
+    // is configured to wake up when either the BufferReleaseReader::ConsumerEndpoint or an eventfd
+    // becomes readable. Interrupts are necessary because a free buffer may become available for
+    // reasons other than a buffer release from the producer.
     class BufferReleaseReader {
     public:
         explicit BufferReleaseReader(BLASTBufferQueue&);
@@ -353,19 +361,6 @@
     };
 
     std::optional<BufferReleaseReader> mBufferReleaseReader;
-
-    std::atomic<int> mThreadsBlockingOnDequeue = 0;
-
-    class BufferReleaseThread {
-    public:
-        BufferReleaseThread(const sp<BLASTBufferQueue>&);
-        ~BufferReleaseThread();
-
-    private:
-        int mEventFd;
-    };
-
-    std::optional<BufferReleaseThread> mBufferReleaseThread;
 #endif
 };
 
diff --git a/libs/gui/include/gui/DisplayLuts.h b/libs/gui/include/gui/DisplayLuts.h
new file mode 100644
index 0000000..16a360d
--- /dev/null
+++ b/libs/gui/include/gui/DisplayLuts.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <android-base/unique_fd.h>
+#include <vector>
+
+namespace android::gui {
+
+struct DisplayLuts {
+public:
+    struct Entry {
+        int32_t dimension;
+        int32_t size;
+        int32_t samplingKey;
+    };
+
+    DisplayLuts() {}
+
+    DisplayLuts(base::unique_fd lutfd, std::vector<int32_t> lutoffsets,
+                std::vector<int32_t> lutdimensions, std::vector<int32_t> lutsizes,
+                std::vector<int32_t> lutsamplingKeys) {
+        fd = std::move(lutfd);
+        offsets = lutoffsets;
+        lutProperties.reserve(offsets.size());
+        for (size_t i = 0; i < lutoffsets.size(); i++) {
+            Entry entry{lutdimensions[i], lutsizes[i], lutsamplingKeys[i]};
+            lutProperties.emplace_back(entry);
+        }
+    }
+
+    base::unique_fd& getLutFileDescriptor() { return fd; }
+
+    std::vector<Entry> lutProperties;
+    std::vector<int32_t> offsets;
+
+private:
+    base::unique_fd fd;
+}; // struct DisplayLuts
+
+} // namespace android::gui
\ No newline at end of file
diff --git a/libs/gui/include/gui/Flags.h b/libs/gui/include/gui/Flags.h
index 735375a..34350d2 100644
--- a/libs/gui/include/gui/Flags.h
+++ b/libs/gui/include/gui/Flags.h
@@ -17,8 +17,20 @@
 #pragma once
 
 #include <com_android_graphics_libgui_flags.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/Surface.h>
 
 #define WB_CAMERA3_AND_PROCESSORS_WITH_DEPENDENCIES                  \
     (COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CAMERA3_AND_PROCESSORS) && \
      COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ) &&  \
-     COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS))
\ No newline at end of file
+     COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS))
+
+#define WB_LIBCAMERASERVICE_WITH_DEPENDENCIES       \
+    (WB_CAMERA3_AND_PROCESSORS_WITH_DEPENDENCIES && \
+     COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_LIBCAMERASERVICE))
+
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+typedef android::Surface SurfaceType;
+#else
+typedef android::IGraphicBufferProducer SurfaceType;
+#endif
\ No newline at end of file
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 00065c8..6bfeaec 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -26,6 +26,7 @@
 #include <android/gui/LayerCaptureArgs.h>
 #include <android/gui/TrustedPresentationThresholds.h>
 #include <android/native_window.h>
+#include <gui/DisplayLuts.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/ITransactionCompletedListener.h>
 #include <math/mat4.h>
@@ -184,6 +185,7 @@
         eCachingHintChanged = 0x00000200,
         eDimmingEnabledChanged = 0x00000400,
         eShadowRadiusChanged = 0x00000800,
+        eLutsChanged = 0x00001000,
         eBufferCropChanged = 0x00002000,
         eRelativeLayerChanged = 0x00004000,
         eReparent = 0x00008000,
@@ -255,7 +257,7 @@
             layer_state_t::eTransformToDisplayInverseChanged |
             layer_state_t::eTransparentRegionChanged |
             layer_state_t::eExtendedRangeBrightnessChanged |
-            layer_state_t::eDesiredHdrHeadroomChanged;
+            layer_state_t::eDesiredHdrHeadroomChanged | layer_state_t::eLutsChanged;
 
     // Content updates.
     static constexpr uint64_t CONTENT_CHANGES = layer_state_t::BUFFER_CHANGES |
@@ -416,6 +418,8 @@
     TrustedPresentationListener trustedPresentationListener;
 
     std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> bufferReleaseChannel;
+
+    std::shared_ptr<gui::DisplayLuts> luts;
 };
 
 class ComposerState {
diff --git a/libs/gui/include/gui/view/Surface.h b/libs/gui/include/gui/view/Surface.h
index 7ddac81..7c762d3 100644
--- a/libs/gui/include/gui/view/Surface.h
+++ b/libs/gui/include/gui/view/Surface.h
@@ -24,7 +24,9 @@
 #include <binder/IBinder.h>
 #include <binder/Parcelable.h>
 
+#include <gui/Flags.h>
 #include <gui/IGraphicBufferProducer.h>
+#include <gui/Surface.h>
 
 namespace android {
 
@@ -46,6 +48,14 @@
     sp<IGraphicBufferProducer> graphicBufferProducer;
     sp<IBinder> surfaceControlHandle;
 
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    // functions used to convert to a parcelable Surface so it can be passed over binder.
+    static Surface fromSurface(const sp<android::Surface>& surface);
+    sp<android::Surface> toSurface() const;
+
+    status_t getUniqueId(/* out */ uint64_t* id) const;
+#endif
+
     virtual status_t writeToParcel(Parcel* parcel) const override;
     virtual status_t readFromParcel(const Parcel* parcel) override;
 
diff --git a/libs/gui/tests/BufferReleaseChannel_test.cpp b/libs/gui/tests/BufferReleaseChannel_test.cpp
index 11d122b..74f69e1 100644
--- a/libs/gui/tests/BufferReleaseChannel_test.cpp
+++ b/libs/gui/tests/BufferReleaseChannel_test.cpp
@@ -29,11 +29,11 @@
 
 // Helper function to check if two file descriptors point to the same file.
 bool is_same_file(int fd1, int fd2) {
-    struct stat stat1;
+    struct stat stat1 {};
     if (fstat(fd1, &stat1) != 0) {
         return false;
     }
-    struct stat stat2;
+    struct stat stat2 {};
     if (fstat(fd2, &stat2) != 0) {
         return false;
     }
@@ -42,7 +42,18 @@
 
 } // namespace
 
-TEST(BufferReleaseChannelTest, MessageFlattenable) {
+class BufferReleaseChannelTest : public testing::Test {
+protected:
+    std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> mConsumer;
+    std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> mProducer;
+
+    void SetUp() override {
+        ASSERT_EQ(OK,
+                  BufferReleaseChannel::open("BufferReleaseChannelTest"s, mConsumer, mProducer));
+    }
+};
+
+TEST_F(BufferReleaseChannelTest, MessageFlattenable) {
     ReleaseCallbackId releaseCallbackId{1, 2};
     sp<Fence> releaseFence = sp<Fence>::make(memfd_create("fake-fence-fd", 0));
     uint32_t maxAcquiredBufferCount = 5;
@@ -92,31 +103,23 @@
 
 // Verify that the BufferReleaseChannel consume returns WOULD_BLOCK when there's no message
 // available.
-TEST(BufferReleaseChannelTest, ConsumerEndpointIsNonBlocking) {
-    std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> consumer;
-    std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> producer;
-    ASSERT_EQ(OK, BufferReleaseChannel::open("test-channel"s, consumer, producer));
-
+TEST_F(BufferReleaseChannelTest, ConsumerEndpointIsNonBlocking) {
     ReleaseCallbackId releaseCallbackId;
     sp<Fence> releaseFence;
     uint32_t maxAcquiredBufferCount;
     ASSERT_EQ(WOULD_BLOCK,
-              consumer->readReleaseFence(releaseCallbackId, releaseFence, maxAcquiredBufferCount));
+              mConsumer->readReleaseFence(releaseCallbackId, releaseFence, maxAcquiredBufferCount));
 }
 
 // Verify that we can write a message to the BufferReleaseChannel producer and read that message
 // using the BufferReleaseChannel consumer.
-TEST(BufferReleaseChannelTest, ProduceAndConsume) {
-    std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> consumer;
-    std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> producer;
-    ASSERT_EQ(OK, BufferReleaseChannel::open("test-channel"s, consumer, producer));
-
+TEST_F(BufferReleaseChannelTest, ProduceAndConsume) {
     sp<Fence> fence = sp<Fence>::make(memfd_create("fake-fence-fd", 0));
 
     for (uint64_t i = 0; i < 64; i++) {
         ReleaseCallbackId producerId{i, i + 1};
         uint32_t maxAcquiredBufferCount = i + 2;
-        ASSERT_EQ(OK, producer->writeReleaseFence(producerId, fence, maxAcquiredBufferCount));
+        ASSERT_EQ(OK, mProducer->writeReleaseFence(producerId, fence, maxAcquiredBufferCount));
     }
 
     for (uint64_t i = 0; i < 64; i++) {
@@ -127,7 +130,7 @@
         sp<Fence> consumerFence;
         uint32_t maxAcquiredBufferCount;
         ASSERT_EQ(OK,
-                  consumer->readReleaseFence(consumerId, consumerFence, maxAcquiredBufferCount));
+                  mConsumer->readReleaseFence(consumerId, consumerFence, maxAcquiredBufferCount));
 
         ASSERT_EQ(expectedId, consumerId);
         ASSERT_TRUE(is_same_file(fence->get(), consumerFence->get()));
@@ -135,4 +138,16 @@
     }
 }
 
+// Verify that BufferReleaseChannel::ConsumerEndpoint's socket can't be written to.
+TEST_F(BufferReleaseChannelTest, ConsumerSocketReadOnly) {
+    uint64_t data = 0;
+    ASSERT_EQ(-1, write(mConsumer->getFd().get(), &data, sizeof(uint64_t)));
+    ASSERT_EQ(errno, EPIPE);
+}
+
+// Verify that BufferReleaseChannel::ProducerEndpoint's socket can't be read from.
+TEST_F(BufferReleaseChannelTest, ProducerSocketWriteOnly) {
+    ASSERT_EQ(0, read(mProducer->getFd().get(), nullptr, sizeof(uint64_t)));
+}
+
 } // namespace android
\ No newline at end of file
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 7d0b512..a481d12 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -294,7 +294,7 @@
                     transactionBody) {
         SurfaceComposerClient::Transaction t;
         transactionBody(t, mSurfaceControl);
-        t.apply(true);
+        t.apply(/*synchronously=*/true);
     }
 
     virtual void showAt(int x, int y, Rect crop = Rect(0, 0, 100, 100)) {
@@ -307,7 +307,7 @@
         t.setAlpha(mSurfaceControl, 1);
         auto reportedListener = sp<SynchronousWindowInfosReportedListener>::make();
         t.addWindowInfosReportedListener(reportedListener);
-        t.apply();
+        t.apply(/*synchronously=*/true);
         reportedListener->wait();
     }
 
@@ -319,7 +319,7 @@
         request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
         request.displayId = displayId.val();
         t.setFocusedWindow(request);
-        t.apply(true);
+        t.apply(/*synchronously=*/true);
     }
 
 public:
@@ -363,7 +363,7 @@
                     transactionBody) override {
         SurfaceComposerClient::Transaction t;
         transactionBody(t, mParentSurfaceControl);
-        t.apply(true);
+        t.apply(/*synchronously=*/true);
     }
 
     void showAt(int x, int y, Rect crop = Rect(0, 0, 100, 100)) override {
@@ -377,7 +377,7 @@
         t.setInputWindowInfo(mSurfaceControl, mInputInfo);
         t.setCrop(mSurfaceControl, crop);
         t.setAlpha(mSurfaceControl, 1);
-        t.apply(true);
+        t.apply(/*synchronously=*/true);
     }
 
 private:
@@ -417,7 +417,7 @@
                 BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE;
         sp<GraphicBuffer> buffer =
                 new GraphicBuffer(w, h, PIXEL_FORMAT_RGBA_8888, 1, usageFlags, "test");
-        Transaction().setBuffer(layer, buffer).apply(true);
+        Transaction().setBuffer(layer, buffer).apply(/*synchronously=*/true);
         usleep(mBufferPostDelay);
     }
 
@@ -1207,7 +1207,7 @@
         t.setDisplayLayerStack(token, layerStack);
         t.setDisplayProjection(token, ui::ROTATION_0, {0, 0, width, height},
                                {offsetX, offsetY, offsetX + width, offsetY + height});
-        t.apply(true);
+        t.apply(/*synchronously=*/true);
 
         mVirtualDisplays.push_back(token);
     }
diff --git a/libs/gui/view/Surface.cpp b/libs/gui/view/Surface.cpp
index 84c2a6a..9f57923 100644
--- a/libs/gui/view/Surface.cpp
+++ b/libs/gui/view/Surface.cpp
@@ -121,6 +121,38 @@
     return str.value_or(String16());
 }
 
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+Surface Surface::fromSurface(const sp<android::Surface>& surface) {
+    if (surface == nullptr) {
+        ALOGE("%s: Error: view::Surface::fromSurface failed due to null surface.", __FUNCTION__);
+        return Surface();
+    }
+    Surface s;
+    s.name = String16(surface->getConsumerName());
+    s.graphicBufferProducer = surface->getIGraphicBufferProducer();
+    s.surfaceControlHandle = surface->getSurfaceControlHandle();
+    return s;
+}
+
+sp<android::Surface> Surface::toSurface() const {
+    if (graphicBufferProducer == nullptr) return nullptr;
+    return new android::Surface(graphicBufferProducer, false, surfaceControlHandle);
+}
+
+status_t Surface::getUniqueId(uint64_t* out_id) const {
+    if (graphicBufferProducer == nullptr) {
+        ALOGE("android::viewSurface::getUniqueId() failed because it's not initialized.");
+        return UNEXPECTED_NULL;
+    }
+    status_t status = graphicBufferProducer->getUniqueId(out_id);
+    if (status != OK) {
+        ALOGE("android::viewSurface::getUniqueId() failed.");
+        return status;
+    }
+    return OK;
+}
+#endif
+
 std::string Surface::toString() const {
     std::stringstream out;
     out << name;
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index 9665de7..d3653cf 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -357,7 +357,8 @@
                 mBatches[deviceId].emplace(msg);
             } else {
                 // consume all pending batches for this device immediately
-                consumeBatchedInputEvents(deviceId, /*requestedFrameTime=*/std::nullopt);
+                consumeBatchedInputEvents(deviceId, /*requestedFrameTime=*/
+                                          std::numeric_limits<nsecs_t>::max());
                 if (canResample &&
                     (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL)) {
                     LOG_IF(INFO, mResamplers.erase(deviceId) == 0)
@@ -480,7 +481,7 @@
 }
 
 std::pair<std::unique_ptr<MotionEvent>, std::optional<uint32_t>>
-InputConsumerNoResampling::createBatchedMotionEvent(const nsecs_t requestedFrameTime,
+InputConsumerNoResampling::createBatchedMotionEvent(const std::optional<nsecs_t> requestedFrameTime,
                                                     std::queue<InputMessage>& messages) {
     std::unique_ptr<MotionEvent> motionEvent;
     std::optional<uint32_t> firstSeqForBatch;
@@ -491,7 +492,11 @@
     const nanoseconds resampleLatency = (resampler != mResamplers.cend())
             ? resampler->second->getResampleLatency()
             : nanoseconds{0};
-    const nanoseconds adjustedFrameTime = nanoseconds{requestedFrameTime} - resampleLatency;
+    // When batching is not enabled, we want to consume all events. That's equivalent to having an
+    // infinite requestedFrameTime.
+    const nanoseconds adjustedFrameTime = (requestedFrameTime.has_value())
+            ? (nanoseconds{*requestedFrameTime} - resampleLatency)
+            : nanoseconds{std::numeric_limits<nsecs_t>::max()};
 
     while (!messages.empty() &&
            (messages.front().body.motion.eventTime <= adjustedFrameTime.count())) {
@@ -513,8 +518,9 @@
     if (!messages.empty()) {
         futureSample = &messages.front();
     }
-    if ((motionEvent != nullptr) && (resampler != mResamplers.cend())) {
-        resampler->second->resampleMotionEvent(nanoseconds{requestedFrameTime}, *motionEvent,
+    if ((motionEvent != nullptr) && (resampler != mResamplers.cend()) &&
+        (requestedFrameTime.has_value())) {
+        resampler->second->resampleMotionEvent(nanoseconds{*requestedFrameTime}, *motionEvent,
                                                futureSample);
     }
 
@@ -524,16 +530,13 @@
 bool InputConsumerNoResampling::consumeBatchedInputEvents(
         std::optional<DeviceId> deviceId, std::optional<nsecs_t> requestedFrameTime) {
     ensureCalledOnLooperThread(__func__);
-    // When batching is not enabled, we want to consume all events. That's equivalent to having an
-    // infinite requestedFrameTime.
-    requestedFrameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max());
     bool producedEvents = false;
 
     for (auto deviceIdIter = (deviceId.has_value()) ? (mBatches.find(*deviceId))
                                                     : (mBatches.begin());
          deviceIdIter != mBatches.cend(); ++deviceIdIter) {
         std::queue<InputMessage>& messages = deviceIdIter->second;
-        auto [motion, firstSeqForBatch] = createBatchedMotionEvent(*requestedFrameTime, messages);
+        auto [motion, firstSeqForBatch] = createBatchedMotionEvent(requestedFrameTime, messages);
         if (motion != nullptr) {
             LOG_ALWAYS_FATAL_IF(!firstSeqForBatch.has_value());
             mCallbacks.onMotionEvent(std::move(motion), *firstSeqForBatch);
diff --git a/libs/input/Resampler.cpp b/libs/input/Resampler.cpp
index e2cc6fb..056db09 100644
--- a/libs/input/Resampler.cpp
+++ b/libs/input/Resampler.cpp
@@ -18,6 +18,7 @@
 
 #include <algorithm>
 #include <chrono>
+#include <iomanip>
 #include <ostream>
 
 #include <android-base/logging.h>
@@ -37,6 +38,11 @@
         true;
 #endif
 
+/**
+ * Log debug messages about timestamp and coordinates of event resampling.
+ * Enable this via "adb shell setprop log.tag.LegacyResamplerResampling DEBUG"
+ * (requires restart)
+ */
 bool debugResampling() {
     if (!IS_DEBUGGABLE_BUILD) {
         static const bool DEBUG_TRANSPORT_RESAMPLING =
@@ -107,46 +113,44 @@
     const size_t latestIndex = numSamples - 1;
     const size_t secondToLatestIndex = (latestIndex > 0) ? (latestIndex - 1) : 0;
     for (size_t sampleIndex = secondToLatestIndex; sampleIndex < numSamples; ++sampleIndex) {
-        std::vector<Pointer> pointers;
-        const size_t numPointers = motionEvent.getPointerCount();
-        for (size_t pointerIndex = 0; pointerIndex < numPointers; ++pointerIndex) {
-            pointers.push_back(Pointer{*(motionEvent.getPointerProperties(pointerIndex)),
-                                       *(motionEvent.getHistoricalRawPointerCoords(pointerIndex,
-                                                                                   sampleIndex))});
+        PointerMap pointerMap;
+        for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount();
+             ++pointerIndex) {
+            pointerMap.insert(Pointer{*(motionEvent.getPointerProperties(pointerIndex)),
+                                      *(motionEvent.getHistoricalRawPointerCoords(pointerIndex,
+                                                                                  sampleIndex))});
         }
         mLatestSamples.pushBack(
-                Sample{nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)}, pointers});
+                Sample{nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)}, pointerMap});
     }
 }
 
 LegacyResampler::Sample LegacyResampler::messageToSample(const InputMessage& message) {
-    std::vector<Pointer> pointers;
+    PointerMap pointerMap;
     for (uint32_t i = 0; i < message.body.motion.pointerCount; ++i) {
-        pointers.push_back(Pointer{message.body.motion.pointers[i].properties,
-                                   message.body.motion.pointers[i].coords});
+        pointerMap.insert(Pointer{message.body.motion.pointers[i].properties,
+                                  message.body.motion.pointers[i].coords});
     }
-    return Sample{nanoseconds{message.body.motion.eventTime}, pointers};
+    return Sample{nanoseconds{message.body.motion.eventTime}, pointerMap};
 }
 
 bool LegacyResampler::pointerPropertiesResampleable(const Sample& target, const Sample& auxiliary) {
-    if (target.pointers.size() > auxiliary.pointers.size()) {
-        LOG_IF(INFO, debugResampling())
-                << "Not resampled. Auxiliary sample has fewer pointers than target sample.";
-        return false;
-    }
-    for (size_t i = 0; i < target.pointers.size(); ++i) {
-        if (target.pointers[i].properties.id != auxiliary.pointers[i].properties.id) {
-            LOG_IF(INFO, debugResampling()) << "Not resampled. Pointer ID mismatch.";
+    for (const Pointer& pointer : target.pointerMap) {
+        const std::optional<Pointer> auxiliaryPointer =
+                auxiliary.pointerMap.find(PointerMap::PointerId{pointer.properties.id});
+        if (!auxiliaryPointer.has_value()) {
+            LOG_IF(INFO, debugResampling())
+                    << "Not resampled. Auxiliary sample does not contain all pointers from target.";
             return false;
         }
-        if (target.pointers[i].properties.toolType != auxiliary.pointers[i].properties.toolType) {
+        if (pointer.properties.toolType != auxiliaryPointer->properties.toolType) {
             LOG_IF(INFO, debugResampling()) << "Not resampled. Pointer ToolType mismatch.";
             return false;
         }
-        if (!canResampleTool(target.pointers[i].properties.toolType)) {
+        if (!canResampleTool(pointer.properties.toolType)) {
             LOG_IF(INFO, debugResampling())
                     << "Not resampled. Cannot resample "
-                    << ftl::enum_string(target.pointers[i].properties.toolType) << " ToolType.";
+                    << ftl::enum_string(pointer.properties.toolType) << " ToolType.";
             return false;
         }
     }
@@ -166,35 +170,40 @@
 
     const nanoseconds delta = futureSample.eventTime - pastSample.eventTime;
     if (delta < RESAMPLE_MIN_DELTA) {
-        LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
+        LOG_IF(INFO, debugResampling())
+                << "Not resampled. Delta is too small: " << std::setprecision(3)
+                << std::chrono::duration<double, std::milli>{delta}.count() << "ms";
         return false;
     }
     return true;
 }
 
 std::optional<LegacyResampler::Sample> LegacyResampler::attemptInterpolation(
-        nanoseconds resampleTime, const InputMessage& futureSample) const {
-    if (!canInterpolate(futureSample)) {
+        nanoseconds resampleTime, const InputMessage& futureMessage) const {
+    if (!canInterpolate(futureMessage)) {
         return std::nullopt;
     }
     LOG_IF(FATAL, mLatestSamples.empty())
             << "Not resampled. mLatestSamples must not be empty to interpolate.";
 
     const Sample& pastSample = *(mLatestSamples.end() - 1);
+    const Sample& futureSample = messageToSample(futureMessage);
 
-    const nanoseconds delta =
-            nanoseconds{futureSample.body.motion.eventTime} - pastSample.eventTime;
+    const nanoseconds delta = nanoseconds{futureSample.eventTime} - pastSample.eventTime;
     const float alpha =
-            std::chrono::duration<float, std::milli>(resampleTime - pastSample.eventTime) / delta;
+            std::chrono::duration<float, std::nano>(resampleTime - pastSample.eventTime) / delta;
 
-    std::vector<Pointer> resampledPointers;
-    for (size_t i = 0; i < pastSample.pointers.size(); ++i) {
-        const PointerCoords& resampledCoords =
-                calculateResampledCoords(pastSample.pointers[i].coords,
-                                         futureSample.body.motion.pointers[i].coords, alpha);
-        resampledPointers.push_back(Pointer{pastSample.pointers[i].properties, resampledCoords});
+    PointerMap resampledPointerMap;
+    for (const Pointer& pointer : pastSample.pointerMap) {
+        if (std::optional<Pointer> futureSamplePointer =
+                    futureSample.pointerMap.find(PointerMap::PointerId{pointer.properties.id});
+            futureSamplePointer.has_value()) {
+            const PointerCoords& resampledCoords =
+                    calculateResampledCoords(pointer.coords, futureSamplePointer->coords, alpha);
+            resampledPointerMap.insert(Pointer{pointer.properties, resampledCoords});
+        }
     }
-    return Sample{resampleTime, resampledPointers};
+    return Sample{resampleTime, resampledPointerMap};
 }
 
 bool LegacyResampler::canExtrapolate() const {
@@ -212,10 +221,14 @@
 
     const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
     if (delta < RESAMPLE_MIN_DELTA) {
-        LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
+        LOG_IF(INFO, debugResampling())
+                << "Not resampled. Delta is too small: " << std::setprecision(3)
+                << std::chrono::duration<double, std::milli>{delta}.count() << "ms";
         return false;
     } else if (delta > RESAMPLE_MAX_DELTA) {
-        LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too large: " << delta << "ns.";
+        LOG_IF(INFO, debugResampling())
+                << "Not resampled. Delta is too large: " << std::setprecision(3)
+                << std::chrono::duration<double, std::milli>{delta}.count() << "ms";
         return false;
     }
     return true;
@@ -241,20 +254,28 @@
             (resampleTime > farthestPrediction) ? (farthestPrediction) : (resampleTime);
     LOG_IF(INFO, debugResampling() && newResampleTime == farthestPrediction)
             << "Resample time is too far in the future. Adjusting prediction from "
-            << (resampleTime - presentSample.eventTime) << " to "
-            << (farthestPrediction - presentSample.eventTime) << "ns.";
+            << std::setprecision(3)
+            << std::chrono::duration<double, std::milli>{resampleTime - presentSample.eventTime}
+                       .count()
+            << "ms to "
+            << std::chrono::duration<double, std::milli>{farthestPrediction -
+                                                         presentSample.eventTime}
+                       .count()
+            << "ms";
     const float alpha =
-            std::chrono::duration<float, std::milli>(newResampleTime - pastSample.eventTime) /
-            delta;
+            std::chrono::duration<float, std::nano>(newResampleTime - pastSample.eventTime) / delta;
 
-    std::vector<Pointer> resampledPointers;
-    for (size_t i = 0; i < presentSample.pointers.size(); ++i) {
-        const PointerCoords& resampledCoords =
-                calculateResampledCoords(pastSample.pointers[i].coords,
-                                         presentSample.pointers[i].coords, alpha);
-        resampledPointers.push_back(Pointer{presentSample.pointers[i].properties, resampledCoords});
+    PointerMap resampledPointerMap;
+    for (const Pointer& pointer : presentSample.pointerMap) {
+        if (std::optional<Pointer> pastSamplePointer =
+                    pastSample.pointerMap.find(PointerMap::PointerId{pointer.properties.id});
+            pastSamplePointer.has_value()) {
+            const PointerCoords& resampledCoords =
+                    calculateResampledCoords(pastSamplePointer->coords, pointer.coords, alpha);
+            resampledPointerMap.insert(Pointer{pointer.properties, resampledCoords});
+        }
     }
-    return Sample{newResampleTime, resampledPointers};
+    return Sample{newResampleTime, resampledPointerMap};
 }
 
 inline void LegacyResampler::addSampleToMotionEvent(const Sample& sample,
@@ -267,6 +288,12 @@
     return RESAMPLE_LATENCY;
 }
 
+/**
+ * The resampler is unaware of ACTION_DOWN. Thus, it needs to constantly check for pointer IDs
+ * occurrences. This problem could be fixed if the resampler has access to the entire stream of
+ * MotionEvent actions. That way, both ACTION_DOWN and ACTION_UP will be visible; therefore,
+ * facilitating pointer tracking between samples.
+ */
 void LegacyResampler::overwriteMotionEventSamples(MotionEvent& motionEvent) const {
     const size_t numSamples = motionEvent.getHistorySize() + 1;
     for (size_t sampleIndex = 0; sampleIndex < numSamples; ++sampleIndex) {
@@ -276,34 +303,59 @@
 }
 
 void LegacyResampler::overwriteStillPointers(MotionEvent& motionEvent, size_t sampleIndex) const {
+    if (!mLastRealSample.has_value() || !mPreviousPrediction.has_value()) {
+        LOG_IF(INFO, debugResampling()) << "Still pointers not overwritten. Not enough data.";
+        return;
+    }
     for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount(); ++pointerIndex) {
+        const std::optional<Pointer> lastRealPointer = mLastRealSample->pointerMap.find(
+                PointerMap::PointerId{motionEvent.getPointerId(pointerIndex)});
+        const std::optional<Pointer> previousPointer = mPreviousPrediction->pointerMap.find(
+                PointerMap::PointerId{motionEvent.getPointerId(pointerIndex)});
+        // This could happen because resampler only receives ACTION_MOVE events.
+        if (!lastRealPointer.has_value() || !previousPointer.has_value()) {
+            continue;
+        }
         const PointerCoords& pointerCoords =
                 *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, sampleIndex));
-        if (equalXY(mLastRealSample->pointers[pointerIndex].coords, pointerCoords)) {
+        if (equalXY(pointerCoords, lastRealPointer->coords)) {
             LOG_IF(INFO, debugResampling())
                     << "Pointer ID: " << motionEvent.getPointerId(pointerIndex)
                     << " did not move. Overwriting its coordinates from " << pointerCoords << " to "
-                    << mLastRealSample->pointers[pointerIndex].coords;
+                    << previousPointer->coords;
             setMotionEventPointerCoords(motionEvent, sampleIndex, pointerIndex,
-                                        mPreviousPrediction->pointers[pointerIndex].coords);
+                                        previousPointer->coords);
         }
     }
 }
 
 void LegacyResampler::overwriteOldPointers(MotionEvent& motionEvent, size_t sampleIndex) const {
     if (!mPreviousPrediction.has_value()) {
+        LOG_IF(INFO, debugResampling()) << "Old sample not overwritten. Not enough data.";
         return;
     }
     if (nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)} <
         mPreviousPrediction->eventTime) {
         LOG_IF(INFO, debugResampling())
                 << "Motion event sample older than predicted sample. Overwriting event time from "
-                << motionEvent.getHistoricalEventTime(sampleIndex) << "ns to "
-                << mPreviousPrediction->eventTime.count() << "ns.";
+                << std::setprecision(3)
+                << std::chrono::duration<double,
+                                         std::milli>{nanoseconds{motionEvent.getHistoricalEventTime(
+                                                             sampleIndex)}}
+                           .count()
+                << "ms to "
+                << std::chrono::duration<double, std::milli>{mPreviousPrediction->eventTime}.count()
+                << "ms";
         for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount();
              ++pointerIndex) {
+            const std::optional<Pointer> previousPointer = mPreviousPrediction->pointerMap.find(
+                    PointerMap::PointerId{motionEvent.getPointerId(pointerIndex)});
+            // This could happen because resampler only receives ACTION_MOVE events.
+            if (!previousPointer.has_value()) {
+                continue;
+            }
             setMotionEventPointerCoords(motionEvent, sampleIndex, pointerIndex,
-                                        mPreviousPrediction->pointers[pointerIndex].coords);
+                                        previousPointer->coords);
         }
     }
 }
@@ -333,6 +385,7 @@
             mPreviousPrediction = sample;
         }
     }
+    LOG_IF(FATAL, mLatestSamples.empty()) << "mLatestSamples must contain at least one sample.";
     mLastRealSample = *(mLatestSamples.end() - 1);
 }
 
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 701fb43..fd77048 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -94,13 +94,6 @@
 }
 
 flag {
-  name: "enable_new_mouse_pointer_ballistics"
-  namespace: "input"
-  description: "Change the acceleration curves for mouse pointer movements to match the touchpad ones"
-  bug: "315313622"
-}
-
-flag {
   name: "rate_limit_user_activity_poke_in_dispatcher"
   namespace: "input"
   description: "Move user-activity poke rate-limiting from PowerManagerService to InputDispatcher."
@@ -214,3 +207,27 @@
   description: "Enable telemetry for rotary input"
   bug: "370353565"
 }
+
+flag {
+  name: "set_input_device_kernel_wake"
+  namespace: "input"
+  description: "Set input device's power/wakeup sysfs node"
+  bug: "372812925"
+}
+
+flag {
+  name: "enable_alphabetic_keyboard_wake"
+  namespace: "input"
+  description: "Enable wake from alphabetic keyboards."
+  bug: "352856881"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "connected_displays_cursor"
+  namespace: "lse_desktop_experience"
+  description: "Allow cursor to transition across multiple connected displays"
+  bug: "362719483"
+}
diff --git a/libs/input/tests/InputConsumerResampling_test.cpp b/libs/input/tests/InputConsumerResampling_test.cpp
index 883ca82..97688a8 100644
--- a/libs/input/tests/InputConsumerResampling_test.cpp
+++ b/libs/input/tests/InputConsumerResampling_test.cpp
@@ -38,6 +38,8 @@
 using std::chrono::nanoseconds;
 using namespace std::chrono_literals;
 
+const std::chrono::milliseconds RESAMPLE_LATENCY{5};
+
 struct Pointer {
     int32_t id{0};
     float x{0.0f};
@@ -440,7 +442,7 @@
             {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
 
     invokeLooperCallback();
-    mConsumer->consumeBatchedInputEvents(nanoseconds{20ms + 5ms /*RESAMPLE_LATENCY*/}.count());
+    mConsumer->consumeBatchedInputEvents(nanoseconds{20ms + RESAMPLE_LATENCY}.count());
 
     // MotionEvent should not resampled because the resample time falls exactly on the existing
     // event time.
@@ -496,14 +498,15 @@
             {40ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
 
     invokeLooperCallback();
-    mConsumer->consumeBatchedInputEvents(nanoseconds{45ms + 5ms /*RESAMPLE_LATENCY*/}.count());
+    mConsumer->consumeBatchedInputEvents(nanoseconds{45ms + RESAMPLE_LATENCY}.count());
+    // Original and resampled event should be both overwritten.
     assertReceivedMotionEvent(
             {InputEventEntry{40ms,
                              {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
-                             AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten
+                             AMOTION_EVENT_ACTION_MOVE},
              InputEventEntry{45ms,
                              {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
-                             AMOTION_EVENT_ACTION_MOVE}}); // resampled event, rewritten
+                             AMOTION_EVENT_ACTION_MOVE}});
 
     mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
     mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
@@ -552,13 +555,14 @@
 
     invokeLooperCallback();
     mConsumer->consumeBatchedInputEvents(nanoseconds{50ms}.count());
+    // Original and resampled event should be both overwritten.
     assertReceivedMotionEvent(
             {InputEventEntry{24ms,
                              {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
-                             AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten
+                             AMOTION_EVENT_ACTION_MOVE},
              InputEventEntry{26ms,
                              {Pointer{.id = 0, .x = 45.0f, .y = 30.0f, .isResampled = true}},
-                             AMOTION_EVENT_ACTION_MOVE}}); // resampled event, rewritten
+                             AMOTION_EVENT_ACTION_MOVE}});
 
     mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
     mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
@@ -566,4 +570,175 @@
     mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
 }
 
+TEST_F(InputConsumerResamplingTest, DoNotResampleWhenFrameTimeIsNotAvailable) {
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent({InputEventEntry{0ms,
+                                               {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
+                                               AMOTION_EVENT_ACTION_DOWN}});
+
+    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(std::nullopt);
+    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);
+}
+
+TEST_F(InputConsumerResamplingTest, TwoPointersAreResampledIndependently) {
+    // Full action for when a pointer with index=1 appears (some other pointer must already be
+    // present)
+    const int32_t actionPointer1Down =
+            AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+    // Full action for when a pointer with index=0 disappears (some other pointer must still remain)
+    const int32_t actionPointer0Up =
+            AMOTION_EVENT_ACTION_POINTER_UP + (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {0ms, {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+    mClientTestChannel->assertNoSentMessages();
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent({InputEventEntry{0ms,
+                                               {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}},
+                                               AMOTION_EVENT_ACTION_DOWN}});
+
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {10ms, {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(nanoseconds{10ms + RESAMPLE_LATENCY}.count());
+    // Not resampled value because requestedFrameTime - RESAMPLE_LATENCY == eventTime
+    assertReceivedMotionEvent({InputEventEntry{10ms,
+                                               {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}},
+                                               AMOTION_EVENT_ACTION_MOVE}});
+
+    // Second pointer id=1 appears
+    mClientTestChannel->enqueueMessage(
+            nextPointerMessage({15ms,
+                                {Pointer{.id = 0, .x = 100.0f, .y = 100.0f},
+                                 Pointer{.id = 1, .x = 500.0f, .y = 500.0f}},
+                                actionPointer1Down}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(nanoseconds{20ms + RESAMPLE_LATENCY}.count());
+    // Not resampled value because requestedFrameTime - RESAMPLE_LATENCY == eventTime.
+    assertReceivedMotionEvent({InputEventEntry{15ms,
+                                               {Pointer{.id = 0, .x = 100.0f, .y = 100.0f},
+                                                Pointer{.id = 1, .x = 500.0f, .y = 500.0f}},
+                                               actionPointer1Down}});
+
+    // Both pointers move
+    mClientTestChannel->enqueueMessage(
+            nextPointerMessage({30ms,
+                                {Pointer{.id = 0, .x = 100.0f, .y = 100.0f},
+                                 Pointer{.id = 1, .x = 500.0f, .y = 500.0f}},
+                                AMOTION_EVENT_ACTION_MOVE}));
+    mClientTestChannel->enqueueMessage(
+            nextPointerMessage({40ms,
+                                {Pointer{.id = 0, .x = 120.0f, .y = 120.0f},
+                                 Pointer{.id = 1, .x = 600.0f, .y = 600.0f}},
+                                AMOTION_EVENT_ACTION_MOVE}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(nanoseconds{45ms + RESAMPLE_LATENCY}.count());
+    assertReceivedMotionEvent(
+            {InputEventEntry{30ms,
+                             {Pointer{.id = 0, .x = 100.0f, .y = 100.0f},
+                              Pointer{.id = 1, .x = 500.0f, .y = 500.0f}},
+                             AMOTION_EVENT_ACTION_MOVE},
+             InputEventEntry{40ms,
+                             {Pointer{.id = 0, .x = 120.0f, .y = 120.0f},
+                              Pointer{.id = 1, .x = 600.0f, .y = 600.0f}},
+                             AMOTION_EVENT_ACTION_MOVE},
+             InputEventEntry{45ms,
+                             {Pointer{.id = 0, .x = 130.0f, .y = 130.0f, .isResampled = true},
+                              Pointer{.id = 1, .x = 650.0f, .y = 650.0f, .isResampled = true}},
+                             AMOTION_EVENT_ACTION_MOVE}});
+
+    // Both pointers move again
+    mClientTestChannel->enqueueMessage(
+            nextPointerMessage({60ms,
+                                {Pointer{.id = 0, .x = 120.0f, .y = 120.0f},
+                                 Pointer{.id = 1, .x = 600.0f, .y = 600.0f}},
+                                AMOTION_EVENT_ACTION_MOVE}));
+    mClientTestChannel->enqueueMessage(
+            nextPointerMessage({70ms,
+                                {Pointer{.id = 0, .x = 130.0f, .y = 130.0f},
+                                 Pointer{.id = 1, .x = 700.0f, .y = 700.0f}},
+                                AMOTION_EVENT_ACTION_MOVE}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(nanoseconds{75ms + RESAMPLE_LATENCY}.count());
+
+    /*
+     * The pointer id 0 at t = 60 should not be equal to 120 because the value was received twice,
+     * and resampled to 130. Therefore, if we reported 130, then we should continue to report it as
+     * such. Likewise, with pointer id 1.
+     */
+
+    // Not 120 because it matches a previous real event.
+    assertReceivedMotionEvent(
+            {InputEventEntry{60ms,
+                             {Pointer{.id = 0, .x = 130.0f, .y = 130.0f, .isResampled = true},
+                              Pointer{.id = 1, .x = 650.0f, .y = 650.0f, .isResampled = true}},
+                             AMOTION_EVENT_ACTION_MOVE},
+             InputEventEntry{70ms,
+                             {Pointer{.id = 0, .x = 130.0f, .y = 130.0f},
+                              Pointer{.id = 1, .x = 700.0f, .y = 700.0f}},
+                             AMOTION_EVENT_ACTION_MOVE},
+             InputEventEntry{75ms,
+                             {Pointer{.id = 0, .x = 135.0f, .y = 135.0f, .isResampled = true},
+                              Pointer{.id = 1, .x = 750.0f, .y = 750.0f, .isResampled = true}},
+                             AMOTION_EVENT_ACTION_MOVE}});
+
+    // First pointer id=0 leaves the screen
+    mClientTestChannel->enqueueMessage(
+            nextPointerMessage({80ms,
+                                {Pointer{.id = 0, .x = 120.0f, .y = 120.0f},
+                                 Pointer{.id = 1, .x = 600.0f, .y = 600.0f}},
+                                actionPointer0Up}));
+
+    invokeLooperCallback();
+    // Not resampled event for ACTION_POINTER_UP
+    assertReceivedMotionEvent({InputEventEntry{80ms,
+                                               {Pointer{.id = 0, .x = 120.0f, .y = 120.0f},
+                                                Pointer{.id = 1, .x = 600.0f, .y = 600.0f}},
+                                               actionPointer0Up}});
+
+    // Remaining pointer id=1 is still present, but doesn't move
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {90ms, {Pointer{.id = 1, .x = 600.0f, .y = 600.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(nanoseconds{100ms}.count());
+
+    /*
+     * The latest event with ACTION_MOVE was at t = 70 with value = 700. Thus, the resampled value
+     * is 700 + ((95 - 70)/(90 - 70))*(600 - 700) = 575.
+     */
+    assertReceivedMotionEvent(
+            {InputEventEntry{90ms,
+                             {Pointer{.id = 1, .x = 600.0f, .y = 600.0f}},
+                             AMOTION_EVENT_ACTION_MOVE},
+             InputEventEntry{95ms,
+                             {Pointer{.id = 1, .x = 575.0f, .y = 575.0f, .isResampled = true}},
+                             AMOTION_EVENT_ACTION_MOVE}});
+}
+
 } // namespace android
diff --git a/libs/input/tests/InputConsumer_test.cpp b/libs/input/tests/InputConsumer_test.cpp
index 6a3bbe5..06e19bb 100644
--- a/libs/input/tests/InputConsumer_test.cpp
+++ b/libs/input/tests/InputConsumer_test.cpp
@@ -194,7 +194,7 @@
     std::unique_ptr<MotionEvent> moveMotionEvent =
             assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE));
     ASSERT_NE(moveMotionEvent, nullptr);
-    EXPECT_EQ(moveMotionEvent->getHistorySize() + 1, 3UL);
+    EXPECT_EQ(moveMotionEvent->getHistorySize() + 1, 2UL);
 
     mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true);
     mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
@@ -443,4 +443,5 @@
     mClientTestChannel->assertFinishMessage(/*seq=*/8, /*handled=*/true);
     mClientTestChannel->assertFinishMessage(/*seq=*/10, /*handled=*/true);
 }
+
 } // namespace android
diff --git a/libs/input/tests/Resampler_test.cpp b/libs/input/tests/Resampler_test.cpp
index fae8518..3162b77 100644
--- a/libs/input/tests/Resampler_test.cpp
+++ b/libs/input/tests/Resampler_test.cpp
@@ -648,7 +648,15 @@
 
     mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
-    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.id = 0,
+                                                       .x = 1.4f,
+                                                       .y = 1.4f,
+                                                       .isResampled = true},
+                                               Pointer{.id = 1,
+                                                       .x = 2.4f,
+                                                       .y = 2.4f,
+                                                       .isResampled = true}});
 }
 
 TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderExtrapolation) {
@@ -670,7 +678,15 @@
 
     mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
-    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+    assertMotionEventIsResampledAndCoordsNear(secondOriginalMotionEvent, secondMotionEvent,
+                                              {Pointer{.id = 1,
+                                                       .x = 4.4f,
+                                                       .y = 4.4f,
+                                                       .isResampled = true},
+                                               Pointer{.id = 0,
+                                                       .x = 3.4f,
+                                                       .y = 3.4f,
+                                                       .isResampled = true}});
 }
 
 TEST_F(ResamplerTest, MultiplePointerDifferentIdsInterpolation) {
diff --git a/libs/input/tests/TestEventMatchers.h b/libs/input/tests/TestEventMatchers.h
index 3589de5..290a97d 100644
--- a/libs/input/tests/TestEventMatchers.h
+++ b/libs/input/tests/TestEventMatchers.h
@@ -75,12 +75,18 @@
     using is_gtest_matcher = void;
     explicit WithMotionActionMatcher(int32_t action) : mAction(action) {}
 
-    bool MatchAndExplain(const MotionEvent& event, std::ostream*) const {
-        bool matches = mAction == event.getAction();
-        if (event.getAction() == AMOTION_EVENT_ACTION_CANCEL) {
-            matches &= (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
+    bool MatchAndExplain(const MotionEvent& event, testing::MatchResultListener* listener) const {
+        if (mAction != event.getAction()) {
+            *listener << "expected " << MotionEvent::actionToString(mAction) << ", but got "
+                      << MotionEvent::actionToString(event.getAction());
+            return false;
         }
-        return matches;
+        if (event.getAction() == AMOTION_EVENT_ACTION_CANCEL &&
+            (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) == 0) {
+            *listener << "event with CANCEL action is missing FLAG_CANCELED";
+            return false;
+        }
+        return true;
     }
 
     void DescribeTo(std::ostream* os) const {
diff --git a/libs/input/tests/TfLiteMotionPredictor_test.cpp b/libs/input/tests/TfLiteMotionPredictor_test.cpp
index c3ac0b7..0c19ebe 100644
--- a/libs/input/tests/TfLiteMotionPredictor_test.cpp
+++ b/libs/input/tests/TfLiteMotionPredictor_test.cpp
@@ -89,23 +89,23 @@
     buffers.pushSample(/*timestamp=*/1,
                        {.position = {.x = 10, .y = 10},
                         .pressure = 0,
-                        .orientation = 0,
-                        .tilt = 0.2});
+                        .tilt = 0.2,
+                        .orientation = 0});
     buffers.pushSample(/*timestamp=*/2,
                        {.position = {.x = 10, .y = 50},
                         .pressure = 0.4,
-                        .orientation = M_PI / 4,
-                        .tilt = 0.3});
+                        .tilt = 0.3,
+                        .orientation = M_PI / 4});
     buffers.pushSample(/*timestamp=*/3,
                        {.position = {.x = 30, .y = 50},
                         .pressure = 0.5,
-                        .orientation = -M_PI / 4,
-                        .tilt = 0.4});
+                        .tilt = 0.4,
+                        .orientation = -M_PI / 4});
     buffers.pushSample(/*timestamp=*/3,
                        {.position = {.x = 30, .y = 60},
                         .pressure = 0,
-                        .orientation = 0,
-                        .tilt = 0.5});
+                        .tilt = 0.5,
+                        .orientation = 0});
     buffers.copyTo(*model);
 
     const int zeroPadding = model->inputLength() - 3;
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 8d8b530..9841c03 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -571,11 +571,12 @@
     std::chrono::nanoseconds frameTime;
     std::vector<InputEventEntry> entries, expectedEntries;
 
-    // full action for when a pointer with id=1 appears (some other pointer must already be present)
+    // full action for when a pointer with index=1 appears (some other pointer must already be
+    // present)
     constexpr int32_t actionPointer1Down =
             AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
-    // full action for when a pointer with id=0 disappears (some other pointer must still remain)
+    // full action for when a pointer with index=0 disappears (some other pointer must still remain)
     constexpr int32_t actionPointer0Up =
             AMOTION_EVENT_ACTION_POINTER_UP + (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index f97eed5..ac3a832 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -263,6 +263,16 @@
     return native_window_set_frame_rate(window, frameRate, compatibility, changeFrameRateStrategy);
 }
 
+int32_t ANativeWindow_setFrameRateParams(
+        ANativeWindow* window, float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
+        ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) {
+    if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) {
+        return -EINVAL;
+    }
+    return native_window_set_frame_rate_params(window, desiredMinRate, desiredMaxRate,
+                                               fixedSourceRate, changeFrameRateStrategy);
+}
+
 /**************************************************************************************************
  * vndk-stable
  **************************************************************************************************/
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index d05ff34..5a78a5c 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -175,6 +175,14 @@
     AHARDWAREBUFFER_FORMAT_YCbCr_P010               = 0x36,
 
     /**
+     * YUV P210 format.
+     * Must have an even width and height. Can be accessed in OpenGL
+     * shaders through an external sampler. Does not support mip-maps
+     * cube-maps or multi-layered textures.
+     */
+    AHARDWAREBUFFER_FORMAT_YCbCr_P210               = 0x3c,
+
+    /**
      * Corresponding formats:
      *   Vulkan: VK_FORMAT_R8_UNORM
      *   OpenGL ES: GR_GL_R8
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index be6623e..bd8d67a 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -33,8 +33,8 @@
 #ifndef ANDROID_NATIVE_WINDOW_H
 #define ANDROID_NATIVE_WINDOW_H
 
-#include <stdint.h>
 #include <stdbool.h>
+#include <stdint.h>
 #include <sys/cdefs.h>
 
 #include <android/data_space.h>
@@ -282,7 +282,7 @@
 void ANativeWindow_tryAllocateBuffers(ANativeWindow* window) __INTRODUCED_IN(30);
 
 /** Change frame rate strategy value for ANativeWindow_setFrameRate. */
-enum ANativeWindow_ChangeFrameRateStrategy {
+typedef enum ANativeWindow_ChangeFrameRateStrategy : int8_t {
     /**
      * Change the frame rate only if the transition is going to be seamless.
      */
@@ -292,7 +292,7 @@
      * i.e. with visual interruptions for the user.
      */
     ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS = 1
-} __INTRODUCED_IN(31);
+} ANativeWindow_ChangeFrameRateStrategy __INTRODUCED_IN(31);
 
 /**
  * Sets the intended frame rate for this window.
@@ -345,6 +345,76 @@
         __INTRODUCED_IN(31);
 
 /**
+ * Sets the intended frame rate for this window.
+ *
+ * On devices that are capable of running the display at different frame rates,
+ * the system may choose a display refresh rate to better match this surface's frame
+ * rate. Usage of this API won't introduce frame rate throttling, or affect other
+ * aspects of the application's frame production pipeline. However, because the system
+ * may change the display refresh rate, calls to this function may result in changes
+ * to Choreographer callback timings, and changes to the time interval at which the
+ * system releases buffers back to the application.
+ *
+ * Note that this only has an effect for surfaces presented on the display. If this
+ * surface is consumed by something other than the system compositor, e.g. a media
+ * codec, this call has no effect.
+ *
+ * You can register for changes in the refresh rate using
+ * \a AChoreographer_registerRefreshRateCallback.
+ *
+ * See ANativeWindow_clearFrameRate().
+ *
+ * Available since API level 36.
+ *
+ * \param window pointer to an ANativeWindow object.
+ *
+ * \param desiredMinRate The desired minimum frame rate (inclusive) for the window, specifying that
+ * the surface prefers the device render rate to be at least `desiredMinRate`.
+ *
+ * <p>Set `desiredMinRate` = `desiredMaxRate` to indicate the surface prefers an exact frame rate.
+ *
+ * <p>Set `desiredMinRate` = 0 to indicate the window has no preference
+ * and any frame rate is acceptable.
+ *
+ * <p>The value should be greater than or equal to 0.
+ *
+ * \param desiredMaxRate The desired maximum frame rate (inclusive) for the window, specifying that
+ * the surface prefers the device render rate to be at most `desiredMaxRate`.
+ *
+ * <p>Set `desiredMaxRate` = `desiredMinRate` to indicate the surface prefers an exact frame rate.
+ *
+ * <p>Set `desiredMaxRate` = positive infinity to indicate the window has no preference
+ * and any frame rate is acceptable.
+ *
+ * <p>The value should be greater than or equal to `desiredMinRate`.
+ *
+ * \param fixedSourceRate The "fixed source" frame rate of the window if the content has an
+ * inherently fixed frame rate, e.g. a video that has a specific frame rate.
+ *
+ * <p>When the frame rate chosen for the surface is the `fixedSourceRate` or a
+ * multiple, the surface can render without frame pulldown, for optimal smoothness. For
+ * example, a 30 fps video (`fixedSourceRate`=30) renders just as smoothly on 30 fps,
+ * 60 fps, 90 fps, 120 fps, and so on.
+ *
+ * <p>Setting the fixed source rate can also be used together with a desired
+ * frame rate min and max via setting `desiredMinRate` and `desiredMaxRate`. This still
+ * means the window's content has a fixed frame rate of `fixedSourceRate`, but additionally
+ * specifies the preference to be in the range [`desiredMinRate`, `desiredMaxRate`]. For example, an
+ * app might want to specify there is 30 fps video (`fixedSourceRate`=30) as well as a smooth
+ * animation on the same window which looks good when drawing within a frame rate range such as
+ * [`desiredMinRate`, `desiredMaxRate`] = [60,120].
+ *
+ * \param changeFrameRateStrategy Whether display refresh rate transitions caused by this surface
+ * should be seamless. A seamless transition is one that doesn't have any visual interruptions, such
+ * as a black screen for a second or two.
+ *
+ * \return 0 for success, -EINVAL if the arguments are invalid.
+ */
+int32_t ANativeWindow_setFrameRateParams(
+        ANativeWindow* window, float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
+        ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) __INTRODUCED_IN(36);
+
+/**
  * Clears the frame rate which is set for this window.
  *
  * This is equivalent to calling
@@ -366,14 +436,13 @@
  *
  * See ANativeWindow_setFrameRateWithChangeStrategy().
  *
- * Available since API level 34.
+ * Available since API level 31.
  *
  * \param window pointer to an ANativeWindow object.
  *
  * \return 0 for success, -EINVAL if the window value is invalid.
  */
-inline int32_t ANativeWindow_clearFrameRate(ANativeWindow* window)
-        __INTRODUCED_IN(__ANDROID_API_U__) {
+inline int32_t ANativeWindow_clearFrameRate(ANativeWindow* window) __INTRODUCED_IN(31) {
     return ANativeWindow_setFrameRateWithChangeStrategy(window, 0,
             ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
             ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 33c303a..05f49ad 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1156,6 +1156,23 @@
                            (int)compatibility, (int)changeFrameRateStrategy);
 }
 
+static inline int native_window_set_frame_rate_params(struct ANativeWindow* window,
+                                                      float desiredMinRate, float desiredMaxRate,
+                                                      float fixedSourceRate,
+                                                      int8_t changeFrameRateStrategy) {
+    // TODO(b/362798998): Fix plumbing to send whole params
+    int compatibility = fixedSourceRate == 0 ? ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT
+                                             : ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+    double frameRate = compatibility == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
+            ? fixedSourceRate
+            : desiredMinRate;
+    if (desiredMaxRate < desiredMinRate) {
+        return -EINVAL;
+    }
+    return window->perform(window, NATIVE_WINDOW_SET_FRAME_RATE, frameRate, compatibility,
+                           changeFrameRateStrategy);
+}
+
 struct ANativeWindowFrameTimelineInfo {
     // Frame Id received from ANativeWindow_getNextFrameId.
     uint64_t frameNumber;
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index e29d5a6..071e354 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -53,6 +53,7 @@
     ANativeWindow_setBuffersTransform;
     ANativeWindow_setDequeueTimeout; # systemapi introduced=30
     ANativeWindow_setFrameRate; # introduced=30
+    ANativeWindow_setFrameRateParams; # introduced=36
     ANativeWindow_setFrameRateWithChangeStrategy; # introduced=31
     ANativeWindow_setSharedBufferMode; # llndk
     ANativeWindow_setSwapInterval; # llndk
diff --git a/libs/nativewindow/rust/Android.bp b/libs/nativewindow/rust/Android.bp
index c572ee7..faab48b 100644
--- a/libs/nativewindow/rust/Android.bp
+++ b/libs/nativewindow/rust/Android.bp
@@ -110,6 +110,7 @@
     name: "libnativewindow_defaults",
     srcs: ["src/lib.rs"],
     rustlibs: [
+        "android.hardware.common-V2-rust",
         "libbinder_rs",
         "libbitflags",
         "libnativewindow_bindgen",
diff --git a/libs/nativewindow/rust/src/handle.rs b/libs/nativewindow/rust/src/handle.rs
index c41ab8d..2b08c1b 100644
--- a/libs/nativewindow/rust/src/handle.rs
+++ b/libs/nativewindow/rust/src/handle.rs
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+use android_hardware_common::{
+    aidl::android::hardware::common::NativeHandle::NativeHandle as AidlNativeHandle,
+    binder::ParcelFileDescriptor,
+};
 use std::{
     ffi::c_int,
     mem::forget,
@@ -81,6 +85,12 @@
 
     /// Destroys the `NativeHandle`, taking ownership of the file descriptors it contained.
     pub fn into_fds(self) -> Vec<OwnedFd> {
+        // Unset FDSan tag since this `native_handle_t` is no longer the owner of the file
+        // descriptors after this function.
+        // SAFETY: Our wrapped `native_handle_t` pointer is always valid.
+        unsafe {
+            ffi::native_handle_unset_fdsan_tag(self.as_ref());
+        }
         let fds = self.data()[..self.fd_count()]
             .iter()
             .map(|fd| {
@@ -190,6 +200,21 @@
     }
 }
 
+impl From<AidlNativeHandle> for NativeHandle {
+    fn from(aidl_native_handle: AidlNativeHandle) -> Self {
+        let fds = aidl_native_handle.fds.into_iter().map(OwnedFd::from).collect();
+        Self::new(fds, &aidl_native_handle.ints).unwrap()
+    }
+}
+
+impl From<NativeHandle> for AidlNativeHandle {
+    fn from(native_handle: NativeHandle) -> Self {
+        let ints = native_handle.ints().to_owned();
+        let fds = native_handle.into_fds().into_iter().map(ParcelFileDescriptor::new).collect();
+        Self { ints, fds }
+    }
+}
+
 // SAFETY: `NativeHandle` owns the `native_handle_t`, which just contains some integers and file
 // descriptors, which aren't tied to any particular thread.
 unsafe impl Send for NativeHandle {}
@@ -240,4 +265,43 @@
 
         drop(cloned);
     }
+
+    #[test]
+    fn to_fds() {
+        let file = File::open("/dev/null").unwrap();
+        let original = NativeHandle::new(vec![file.into()], &[42]).unwrap();
+        assert_eq!(original.ints(), &[42]);
+        assert_eq!(original.fds().len(), 1);
+
+        let fds = original.into_fds();
+        assert_eq!(fds.len(), 1);
+    }
+
+    #[test]
+    fn to_aidl() {
+        let file = File::open("/dev/null").unwrap();
+        let original = NativeHandle::new(vec![file.into()], &[42]).unwrap();
+        assert_eq!(original.ints(), &[42]);
+        assert_eq!(original.fds().len(), 1);
+
+        let aidl = AidlNativeHandle::from(original);
+        assert_eq!(&aidl.ints, &[42]);
+        assert_eq!(aidl.fds.len(), 1);
+    }
+
+    #[test]
+    fn to_from_aidl() {
+        let file = File::open("/dev/null").unwrap();
+        let original = NativeHandle::new(vec![file.into()], &[42]).unwrap();
+        assert_eq!(original.ints(), &[42]);
+        assert_eq!(original.fds().len(), 1);
+
+        let aidl = AidlNativeHandle::from(original);
+        assert_eq!(&aidl.ints, &[42]);
+        assert_eq!(aidl.fds.len(), 1);
+
+        let converted_back = NativeHandle::from(aidl);
+        assert_eq!(converted_back.ints(), &[42]);
+        assert_eq!(converted_back.fds().len(), 1);
+    }
 }
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index f19b908..014c912 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -203,8 +203,8 @@
         Self(buffer_ptr)
     }
 
-    /// Creates a new Rust HardwareBuffer to wrap the given AHardwareBuffer without taking ownership
-    /// of it.
+    /// Creates a new Rust HardwareBuffer to wrap the given `AHardwareBuffer` without taking
+    /// ownership of it.
     ///
     /// Unlike [`from_raw`](Self::from_raw) this method will increment the refcount on the buffer.
     /// This means that the caller can continue to use the raw buffer it passed in, and must call
@@ -220,8 +220,20 @@
         Self(buffer)
     }
 
-    /// Get the internal |AHardwareBuffer| pointer without decrementing the refcount. This can
-    /// be used to provide a pointer to the AHB for a C/C++ API over the FFI.
+    /// Returns the internal `AHardwareBuffer` pointer.
+    ///
+    /// This is only valid as long as this `HardwareBuffer` exists, so shouldn't be stored. It can
+    /// be used to provide a pointer for a C/C++ API over FFI.
+    pub fn as_raw(&self) -> NonNull<AHardwareBuffer> {
+        self.0
+    }
+
+    /// Gets the internal `AHardwareBuffer` pointer without decrementing the refcount. This can
+    /// be used for a C/C++ API which takes ownership of the pointer.
+    ///
+    /// The caller is responsible for releasing the `AHardwareBuffer` pointer by calling
+    /// `AHardwareBuffer_release` when it is finished with it, or may convert it back to a Rust
+    /// `HardwareBuffer` by calling [`HardwareBuffer::from_raw`].
     pub fn into_raw(self) -> NonNull<AHardwareBuffer> {
         let buffer = ManuallyDrop::new(self);
         buffer.0
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index 859ae8b..ac43da8 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <gui/DisplayLuts.h>
 #include <math/mat4.h>
 #include <math/vec3.h>
 #include <renderengine/ExternalTexture.h>
@@ -145,6 +146,8 @@
     // If white point nits are unknown, then this layer is assumed to have the
     // same luminance as the brightest layer in the scene.
     float whitePointNits = -1.f;
+
+    std::shared_ptr<gui::DisplayLuts> luts;
 };
 
 // Keep in sync with custom comparison function in
@@ -187,7 +190,7 @@
             lhs.blurRegionTransform == rhs.blurRegionTransform &&
             lhs.stretchEffect == rhs.stretchEffect &&
             lhs.edgeExtensionEffect == rhs.edgeExtensionEffect &&
-            lhs.whitePointNits == rhs.whitePointNits;
+            lhs.whitePointNits == rhs.whitePointNits && lhs.luts == rhs.luts;
 }
 
 static inline void PrintTo(const Buffer& settings, ::std::ostream* os) {
diff --git a/libs/tracing_perfetto/Android.bp b/libs/tracing_perfetto/Android.bp
index b5c56c5..9a2d4f7 100644
--- a/libs/tracing_perfetto/Android.bp
+++ b/libs/tracing_perfetto/Android.bp
@@ -47,4 +47,6 @@
     ],
 
     host_supported: true,
+    // for vndbinder
+    vendor_available: true,
 }
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp
index 5159ffe..b19a862 100644
--- a/opengl/libs/Android.bp
+++ b/opengl/libs/Android.bp
@@ -135,6 +135,9 @@
         "EGL/MultifileBlobCache.cpp",
     ],
     export_include_dirs: ["EGL"],
+    shared_libs: [
+        "libz",
+    ],
 }
 
 cc_library_shared {
@@ -169,6 +172,7 @@
         "libutils",
         "libSurfaceFlingerProp",
         "libunwindstack",
+        "libz",
     ],
     static_libs: [
         "libEGL_getProcAddress",
@@ -199,6 +203,7 @@
     ],
     shared_libs: [
         "libutils",
+        "libz",
     ],
 }
 
diff --git a/opengl/libs/EGL/FileBlobCache.cpp b/opengl/libs/EGL/FileBlobCache.cpp
index 4a0fac4..573ca54 100644
--- a/opengl/libs/EGL/FileBlobCache.cpp
+++ b/opengl/libs/EGL/FileBlobCache.cpp
@@ -27,6 +27,7 @@
 
 #include <log/log.h>
 #include <utils/Trace.h>
+#include <zlib.h>
 
 // Cache file header
 static const char* cacheFileMagic = "EGL$";
@@ -34,20 +35,10 @@
 
 namespace android {
 
-uint32_t crc32c(const uint8_t* buf, size_t len) {
-    const uint32_t polyBits = 0x82F63B78;
-    uint32_t r = 0;
-    for (size_t i = 0; i < len; i++) {
-        r ^= buf[i];
-        for (int j = 0; j < 8; j++) {
-            if (r & 1) {
-                r = (r >> 1) ^ polyBits;
-            } else {
-                r >>= 1;
-            }
-        }
-    }
-    return r;
+uint32_t GenerateCRC32(const uint8_t *data, size_t size)
+{
+    const unsigned long initialValue = crc32_z(0u, nullptr, 0u);
+    return static_cast<uint32_t>(crc32_z(initialValue, data, size));
 }
 
 FileBlobCache::FileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
@@ -101,7 +92,7 @@
             return;
         }
         uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
-        if (crc32c(buf + headerSize, cacheSize) != *crc) {
+        if (GenerateCRC32(buf + headerSize, cacheSize) != *crc) {
             ALOGE("cache file failed CRC check");
             close(fd);
             return;
@@ -175,7 +166,7 @@
         // Write the file magic and CRC
         memcpy(buf, cacheFileMagic, 4);
         uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
-        *crc = crc32c(buf + headerSize, cacheSize);
+        *crc = GenerateCRC32(buf + headerSize, cacheSize);
 
         if (write(fd, buf, fileSize) == -1) {
             ALOGE("error writing cache file: %s (%d)", strerror(errno),
diff --git a/opengl/libs/EGL/FileBlobCache.h b/opengl/libs/EGL/FileBlobCache.h
index f083b0d..224444d 100644
--- a/opengl/libs/EGL/FileBlobCache.h
+++ b/opengl/libs/EGL/FileBlobCache.h
@@ -22,7 +22,7 @@
 
 namespace android {
 
-uint32_t crc32c(const uint8_t* buf, size_t len);
+uint32_t GenerateCRC32(const uint8_t *data, size_t size);
 
 class FileBlobCache : public BlobCache {
 public:
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
index 9905210..f7e33b3 100644
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -214,9 +214,8 @@
                 }
 
                 // Ensure we have a good CRC
-                if (header.crc !=
-                    crc32c(mappedEntry + sizeof(MultifileHeader),
-                           fileSize - sizeof(MultifileHeader))) {
+                if (header.crc != GenerateCRC32(mappedEntry + sizeof(MultifileHeader),
+                                                fileSize - sizeof(MultifileHeader))) {
                     ALOGV("INIT: Entry %u failed CRC check! Removing.", entryHash);
                     if (remove(fullPath.c_str()) != 0) {
                         ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
@@ -532,9 +531,9 @@
             mBuildId.length() > PROP_VALUE_MAX ? PROP_VALUE_MAX : mBuildId.length());
 
     // Finally update the crc, using cacheVersion and everything the follows
-    status.crc =
-            crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
-                   sizeof(status) - offsetof(MultifileStatus, cacheVersion));
+    status.crc = GenerateCRC32(
+        reinterpret_cast<uint8_t *>(&status) + offsetof(MultifileStatus, cacheVersion),
+        sizeof(status) - offsetof(MultifileStatus, cacheVersion));
 
     // Create the status file
     std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
@@ -599,9 +598,9 @@
     }
 
     // Ensure we have a good CRC
-    if (status.crc !=
-        crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
-               sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
+    if (status.crc != GenerateCRC32(reinterpret_cast<uint8_t *>(&status) +
+                                        offsetof(MultifileStatus, cacheVersion),
+                                    sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
         ALOGE("STATUS(CHECK): Cache status failed CRC check!");
         return false;
     }
@@ -840,8 +839,8 @@
 
             // Add CRC check to the header (always do this last!)
             MultifileHeader* header = reinterpret_cast<MultifileHeader*>(buffer);
-            header->crc =
-                    crc32c(buffer + sizeof(MultifileHeader), bufferSize - sizeof(MultifileHeader));
+            header->crc             = GenerateCRC32(buffer + sizeof(MultifileHeader),
+                                                    bufferSize - sizeof(MultifileHeader));
 
             ssize_t result = write(fd, buffer, bufferSize);
             if (result != bufferSize) {
diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h
index 18566c2..65aa2db 100644
--- a/opengl/libs/EGL/MultifileBlobCache.h
+++ b/opengl/libs/EGL/MultifileBlobCache.h
@@ -34,7 +34,7 @@
 
 namespace android {
 
-constexpr uint32_t kMultifileBlobCacheVersion = 1;
+constexpr uint32_t kMultifileBlobCacheVersion = 2;
 constexpr char kMultifileBlobCacheStatusFile[] = "cache.status";
 
 struct MultifileHeader {
diff --git a/opengl/libs/EGL/fuzzer/Android.bp b/opengl/libs/EGL/fuzzer/Android.bp
index 022a2a3..4947e5f 100644
--- a/opengl/libs/EGL/fuzzer/Android.bp
+++ b/opengl/libs/EGL/fuzzer/Android.bp
@@ -36,6 +36,10 @@
         "libutils",
     ],
 
+    shared_libs: [
+        "libz",
+    ],
+
     srcs: [
         "MultifileBlobCache_fuzzer.cpp",
     ],
diff --git a/opengl/tests/EGLTest/EGL_test.cpp b/opengl/tests/EGLTest/EGL_test.cpp
index 503d7df..839a5ca 100644
--- a/opengl/tests/EGLTest/EGL_test.cpp
+++ b/opengl/tests/EGLTest/EGL_test.cpp
@@ -765,6 +765,30 @@
     }
 }
 
+// Verify that eglCreateContext works when EGL_TELEMETRY_HINT_ANDROID is used with
+// NO_HINT = 0, SKIP_TELEMETRY = 1 and an invalid of value of 2
+TEST_F(EGLTest, EGLContextTelemetryHintExt) {
+    for (int i = 0; i < 3; i++) {
+        EGLConfig config;
+        get8BitConfig(config);
+        std::vector<EGLint> contextAttributes;
+        contextAttributes.reserve(4);
+        contextAttributes.push_back(EGL_TELEMETRY_HINT_ANDROID);
+        contextAttributes.push_back(i);
+        contextAttributes.push_back(EGL_NONE);
+        contextAttributes.push_back(EGL_NONE);
+
+        EGLContext eglContext = eglCreateContext(mEglDisplay, config, EGL_NO_CONTEXT,
+                                                 contextAttributes.data());
+        EXPECT_NE(EGL_NO_CONTEXT, eglContext);
+        EXPECT_EQ(EGL_SUCCESS, eglGetError());
+
+        if (eglContext != EGL_NO_CONTEXT) {
+            eglDestroyContext(mEglDisplay, eglContext);
+        }
+    }
+}
+
 // Emulate what a native application would do to create a
 // 10:10:10:2 surface.
 TEST_F(EGLTest, EGLConfig1010102) {
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 756a29b..6ef4a3d 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -137,19 +137,18 @@
     ui::LogicalDisplayId defaultPointerDisplayId;
 
     // The mouse pointer speed, as a number from -7 (slowest) to 7 (fastest).
-    //
-    // Currently only used when the enable_new_mouse_pointer_ballistics flag is enabled.
     int32_t mousePointerSpeed;
 
     // Displays on which an acceleration curve shouldn't be applied for pointer movements from mice.
-    //
-    // Currently only used when the enable_new_mouse_pointer_ballistics flag is enabled.
     std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled;
 
-    // Velocity control parameters for mouse pointer movements.
+    // Velocity control parameters for touchpad pointer movements on the old touchpad stack (based
+    // on TouchInputMapper).
     //
-    // If the enable_new_mouse_pointer_ballistics flag is enabled, these are ignored and the values
-    // of mousePointerSpeed and mousePointerAccelerationEnabled used instead.
+    // For mice, these are ignored and the values of mousePointerSpeed and
+    // mousePointerAccelerationEnabled used instead.
+    //
+    // TODO(b/281840344): remove this.
     VelocityControlParameters pointerVelocityControlParameters;
 
     // Velocity control parameters for mouse wheel movements.
@@ -426,6 +425,16 @@
 
     /* Notifies that mouse cursor faded due to typing. */
     virtual void notifyMouseCursorFadedOnTyping() = 0;
+
+    /* Set whether the given input device can wake up the kernel from sleep
+     * when it generates input events. By default, usually only internal (built-in)
+     * input devices can wake the kernel from sleep. For an external input device
+     * that supports remote wakeup to be able to wake the kernel, this must be called
+     * after each time the device is connected/added.
+     *
+     * Returns true if setting power wakeup was successful.
+     */
+    virtual bool setKernelWakeEnabled(int32_t deviceId, bool enabled) = 0;
 };
 
 // --- TouchAffineTransformation ---
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index f8cd973..35ba48f 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -43,6 +43,7 @@
 #include <android-base/strings.h>
 #include <cutils/properties.h>
 #include <ftl/enum.h>
+#include <input/InputEventLabels.h>
 #include <input/KeyCharacterMap.h>
 #include <input/KeyLayoutMap.h>
 #include <input/PrintTools.h>
@@ -1021,6 +1022,8 @@
     std::scoped_lock _l(mLock);
     const Device* device = getDeviceLocked(deviceId);
     if (device == nullptr) {
+        ALOGE("Couldn't find device with ID %d, so returning null axis info for axis %s", deviceId,
+              InputEventLookup::getLinuxEvdevLabel(EV_ABS, axis, 0).code.c_str());
         return std::nullopt;
     }
     // We can read the RawAbsoluteAxisInfo even if the device is disabled and doesn't have a valid
@@ -2848,6 +2851,35 @@
     mNeedToReopenDevices = true;
 }
 
+bool EventHub::setKernelWakeEnabled(int32_t deviceId, bool enabled) {
+    std::scoped_lock _l(mLock);
+    std::string enabledStr = enabled ? "enabled" : "disabled";
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) {
+        ALOGE("Device Id %d does not exist for setting power wakeup", deviceId);
+        return false;
+    }
+    if (device->associatedDevice == nullptr) {
+        return false;
+    }
+    std::filesystem::path currentPath = device->associatedDevice->sysfsRootPath;
+    while (!currentPath.empty() && currentPath != "/") {
+        std::string nodePath = currentPath / "power/wakeup";
+        if (std::filesystem::exists(nodePath)) {
+            if (base::WriteStringToFile(enabledStr, nodePath)) {
+                return true;
+
+            }
+            // No need to continue searching in parent directories as power/wakeup nodes
+            // higher up may control other subdevices.
+            ALOGW("Failed to set power/wakeup node at %s", nodePath.c_str());
+            return false;
+        }
+        currentPath = currentPath.parent_path();
+    }
+    return false;
+}
+
 void EventHub::dump(std::string& dump) const {
     dump += "Event Hub State:\n";
 
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 02eeb0a..5e42d57 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -745,6 +745,14 @@
     }
 }
 
+bool InputDevice::setKernelWakeEnabled(bool enabled) {
+    bool success = false;
+    for_each_subdevice([&enabled, &success](InputDeviceContext& context) {
+        success |= context.setKernelWakeEnabled(enabled);
+    });
+    return success;
+}
+
 InputDeviceContext::InputDeviceContext(InputDevice& device, int32_t eventHubId)
       : mDevice(device),
         mContext(device.getContext()),
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index ab27042..a2b7e82 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -901,6 +901,16 @@
     mPreventingTouchpadTaps = true;
 }
 
+bool InputReader::setKernelWakeEnabled(int32_t deviceId, bool enabled) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device) {
+        return device->setKernelWakeEnabled(enabled);
+    }
+    return false;
+}
+
 void InputReader::dump(std::string& dump) {
     std::scoped_lock _l(mLock);
 
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index dffd8e3..4336945 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -396,6 +396,13 @@
     /* Sysfs node changed. Reopen the Eventhub device if any new Peripheral like Light, Battery,
      * etc. is detected. */
     virtual void sysfsNodeChanged(const std::string& sysfsNodePath) = 0;
+
+    /* Set whether the given input device can wake up the kernel from sleep
+     * when it generates input events. By default, usually only internal (built-in)
+     * input devices can wake the kernel from sleep. For an external input device
+     * that supports remote wakeup to be able to wake the kernel, this must be called
+     * after each time the device is connected/added. */
+    virtual bool setKernelWakeEnabled(int32_t deviceId, bool enabled) = 0;
 };
 
 template <std::size_t BITS>
@@ -603,6 +610,8 @@
 
     void sysfsNodeChanged(const std::string& sysfsNodePath) override final;
 
+    bool setKernelWakeEnabled(int32_t deviceId, bool enabled) override final;
+
     ~EventHub() override;
 
 private:
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 8958d9e..abe7a5f 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -139,6 +139,8 @@
 
     std::optional<HardwareProperties> getTouchpadHardwareProperties();
 
+    bool setKernelWakeEnabled(bool enabled);
+
     // construct and add a mapper to the input device
     template <class T, typename... Args>
     T& addMapper(int32_t eventHubId, Args... args) {
@@ -469,6 +471,9 @@
     inline void setKeyboardType(KeyboardType keyboardType) {
         return mDevice.setKeyboardType(keyboardType);
     }
+    inline bool setKernelWakeEnabled(bool enabled) {
+        return mEventHub->setKernelWakeEnabled(mId, enabled);
+    }
 
 private:
     InputDevice& mDevice;
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 1003871..7614a05 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -120,6 +120,8 @@
 
     void notifyMouseCursorFadedOnTyping() override;
 
+    bool setKernelWakeEnabled(int32_t deviceId, bool enabled) override;
+
 protected:
     // These members are protected so they can be instrumented by test cases.
     virtual std::shared_ptr<InputDevice> createDeviceLocked(nsecs_t when, int32_t deviceId,
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 630bd9b..b33659c 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -33,8 +33,6 @@
 
 #include "input/PrintTools.h"
 
-namespace input_flags = com::android::input::flags;
-
 namespace android {
 
 // The default velocity control parameters that has no effect.
@@ -77,8 +75,7 @@
 CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext,
                                      const InputReaderConfiguration& readerConfig)
       : InputMapper(deviceContext, readerConfig),
-        mLastEventTime(std::numeric_limits<nsecs_t>::min()),
-        mEnableNewMousePointerBallistics(input_flags::enable_new_mouse_pointer_ballistics()) {}
+        mLastEventTime(std::numeric_limits<nsecs_t>::min()) {}
 
 uint32_t CursorInputMapper::getSources() const {
     return mSource;
@@ -207,8 +204,7 @@
     mDownTime = 0;
     mLastEventTime = std::numeric_limits<nsecs_t>::min();
 
-    mOldPointerVelocityControl.reset();
-    mNewPointerVelocityControl.reset();
+    mPointerVelocityControl.reset();
     mWheelXVelocityControl.reset();
     mWheelYVelocityControl.reset();
 
@@ -291,11 +287,7 @@
     mWheelYVelocityControl.move(when, nullptr, &vscroll);
     mWheelXVelocityControl.move(when, &hscroll, nullptr);
 
-    if (mEnableNewMousePointerBallistics) {
-        mNewPointerVelocityControl.move(when, &deltaX, &deltaY);
-    } else {
-        mOldPointerVelocityControl.move(when, &deltaX, &deltaY);
-    }
+    mPointerVelocityControl.move(when, &deltaX, &deltaY);
 
     float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
@@ -486,27 +478,15 @@
 void CursorInputMapper::configureOnChangePointerSpeed(const InputReaderConfiguration& config) {
     if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) {
         // Disable any acceleration or scaling for the pointer when Pointer Capture is enabled.
-        if (mEnableNewMousePointerBallistics) {
-            mNewPointerVelocityControl.setAccelerationEnabled(false);
-        } else {
-            mOldPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
-        }
+        mPointerVelocityControl.setAccelerationEnabled(false);
         mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
         mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
     } else {
-        if (mEnableNewMousePointerBallistics) {
-            mNewPointerVelocityControl.setAccelerationEnabled(
-                    config.displaysWithMousePointerAccelerationDisabled.count(
-                            mDisplayId.value_or(ui::LogicalDisplayId::INVALID)) == 0);
-            mNewPointerVelocityControl.setCurve(
-                    createAccelerationCurveForPointerSensitivity(config.mousePointerSpeed));
-        } else {
-            mOldPointerVelocityControl.setParameters(
-                    (config.displaysWithMousePointerAccelerationDisabled.count(
-                             mDisplayId.value_or(ui::LogicalDisplayId::INVALID)) == 0)
-                            ? config.pointerVelocityControlParameters
-                            : FLAT_VELOCITY_CONTROL_PARAMS);
-        }
+        mPointerVelocityControl.setAccelerationEnabled(
+                config.displaysWithMousePointerAccelerationDisabled.count(
+                        mDisplayId.value_or(ui::LogicalDisplayId::INVALID)) == 0);
+        mPointerVelocityControl.setCurve(
+                createAccelerationCurveForPointerSensitivity(config.mousePointerSpeed));
         mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters);
         mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters);
     }
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 403e96d..8319922 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -104,8 +104,7 @@
 
     // Velocity controls for mouse pointer and wheel movements.
     // The controls for X and Y wheel movements are separate to keep them decoupled.
-    SimpleVelocityControl mOldPointerVelocityControl;
-    CurvedVelocityControl mNewPointerVelocityControl;
+    CurvedVelocityControl mPointerVelocityControl;
     SimpleVelocityControl mWheelXVelocityControl;
     SimpleVelocityControl mWheelYVelocityControl;
 
@@ -120,7 +119,6 @@
     nsecs_t mDownTime;
     nsecs_t mLastEventTime;
 
-    const bool mEnableNewMousePointerBallistics;
     bool mMouseReverseVerticalScrolling = false;
 
     explicit CursorInputMapper(InputDeviceContext& deviceContext,
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index a433a72..fe3e4c2 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -344,12 +344,14 @@
     }
 
     KeyboardType keyboardType = getDeviceContext().getKeyboardType();
-    // Any key down on an external keyboard should wake the device.
-    // We don't do this for internal keyboards to prevent them from waking up in your pocket.
+    // Any key down on an external keyboard or internal alphanumeric keyboard should wake the
+    // device. We don't do this for non-alphanumeric internal keyboards to prevent them from
+    // waking up in your pocket.
     // For internal keyboards and devices for which the default wake behavior is explicitly
     // prevented (e.g. TV remotes), the key layout file should specify the policy flags for each
     // wake key individually.
-    if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault &&
+    if (down && !mParameters.doNotWakeByDefault &&
+        (getDeviceContext().isExternal() || wakeOnAlphabeticKeyboard(keyboardType)) &&
         !(keyboardType != KeyboardType::ALPHABETIC && isMediaKey(keyCode))) {
         policyFlags |= POLICY_FLAG_WAKE;
     }
@@ -507,4 +509,8 @@
     return deviceSources & ALL_KEYBOARD_SOURCES;
 }
 
+bool KeyboardInputMapper::wakeOnAlphabeticKeyboard(const KeyboardType keyboardType) const {
+    return mEnableAlphabeticKeyboardWakeFlag && (KeyboardType::ALPHABETIC == keyboardType);
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 10bd424..7d9b3e4 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <com_android_input_flags.h>
+
 #include "HidUsageAccumulator.h"
 #include "InputMapper.h"
 
@@ -85,6 +87,10 @@
         bool doNotWakeByDefault{};
     } mParameters{};
 
+    // Store the value of enable wake for alphanumeric keyboard flag.
+    const bool mEnableAlphabeticKeyboardWakeFlag =
+            com::android::input::flags::enable_alphabetic_keyboard_wake();
+
     KeyboardInputMapper(InputDeviceContext& deviceContext,
                         const InputReaderConfiguration& readerConfig, uint32_t source);
     void configureParameters();
@@ -109,6 +115,8 @@
     [[nodiscard]] std::list<NotifyArgs> cancelAllDownKeys(nsecs_t when);
     void onKeyDownProcessed(nsecs_t downTime);
     uint32_t getEventSource() const;
+
+    bool wakeOnAlphabeticKeyboard(const KeyboardType keyboardType) const;
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 744cf4a..600ae52 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -78,6 +78,7 @@
         "PreferStylusOverTouch_test.cpp",
         "PropertyProvider_test.cpp",
         "RotaryEncoderInputMapper_test.cpp",
+        "SensorInputMapper_test.cpp",
         "SlopController_test.cpp",
         "SwitchInputMapper_test.cpp",
         "SyncQueue_test.cpp",
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index 1762a45..d4e8fdf 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -25,8 +25,10 @@
 #include <android-base/logging.h>
 #include <android_companion_virtualdevice_flags.h>
 #include <com_android_input_flags.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/DisplayViewport.h>
+#include <input/InputEventLabels.h>
 #include <linux/input-event-codes.h>
 #include <linux/input.h>
 #include <utils/Timers.h>
@@ -52,6 +54,8 @@
 constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
 constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
 constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION;
+constexpr auto AXIS_X = AMOTION_EVENT_AXIS_X;
+constexpr auto AXIS_Y = AMOTION_EVENT_AXIS_Y;
 constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
 constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1};
 constexpr int32_t DISPLAY_WIDTH = 480;
@@ -94,9 +98,35 @@
     return v;
 }
 
+// In a number of these tests, we want to check that some pointer motion is reported without
+// specifying an exact value, as that would require updating the tests every time the pointer
+// ballistics was changed. To do this, we make some matchers that only check the sign of a
+// particular axis.
+MATCHER_P(WithPositiveAxis, axis, "MotionEvent with a positive axis value") {
+    *result_listener << "expected 1 pointer with a positive "
+                     << InputEventLookup::getAxisLabel(axis) << " axis but got "
+                     << arg.pointerCoords.size() << " pointers, with axis value "
+                     << arg.pointerCoords[0].getAxisValue(axis);
+    return arg.pointerCoords.size() == 1 && arg.pointerCoords[0].getAxisValue(axis) > 0;
+}
+
+MATCHER_P(WithZeroAxis, axis, "MotionEvent with a zero axis value") {
+    *result_listener << "expected 1 pointer with a zero " << InputEventLookup::getAxisLabel(axis)
+                     << " axis but got " << arg.pointerCoords.size()
+                     << " pointers, with axis value " << arg.pointerCoords[0].getAxisValue(axis);
+    return arg.pointerCoords.size() == 1 && arg.pointerCoords[0].getAxisValue(axis) == 0;
+}
+
+MATCHER_P(WithNegativeAxis, axis, "MotionEvent with a negative axis value") {
+    *result_listener << "expected 1 pointer with a negative "
+                     << InputEventLookup::getAxisLabel(axis) << " axis but got "
+                     << arg.pointerCoords.size() << " pointers, with axis value "
+                     << arg.pointerCoords[0].getAxisValue(axis);
+    return arg.pointerCoords.size() == 1 && arg.pointerCoords[0].getAxisValue(axis) < 0;
+}
+
 } // namespace
 
-namespace input_flags = com::android::input::flags;
 namespace vd_flags = android::companion::virtualdevice::flags;
 
 /**
@@ -150,24 +180,21 @@
         ASSERT_GT(mDevice->getGeneration(), generation);
     }
 
-    void testMotionRotation(int32_t originalX, int32_t originalY, int32_t rotatedX,
-                            int32_t rotatedY) {
+    void testRotation(int32_t originalX, int32_t originalY,
+                      const testing::Matcher<NotifyMotionArgs>& coordsMatcher) {
         std::list<NotifyArgs> args;
         args += process(ARBITRARY_TIME, EV_REL, REL_X, originalX);
         args += process(ARBITRARY_TIME, EV_REL, REL_Y, originalY);
         args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
         ASSERT_THAT(args,
                     ElementsAre(VariantWith<NotifyMotionArgs>(
-                            AllOf(WithMotionAction(ACTION_MOVE),
-                                  WithCoords(float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD,
-                                             float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD)))));
+                            AllOf(WithMotionAction(ACTION_MOVE), coordsMatcher))));
     }
 };
 
 class CursorInputMapperUnitTest : public CursorInputMapperUnitTestBase {
 protected:
     void SetUp() override {
-        input_flags::enable_new_mouse_pointer_ballistics(false);
         vd_flags::high_resolution_scroll(false);
         CursorInputMapperUnitTestBase::SetUp();
     }
@@ -344,14 +371,12 @@
     args += process(EV_KEY, BTN_MOUSE, 0);
     args += process(EV_SYN, SYN_REPORT, 0);
     ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(BUTTON_RELEASE),
-                                          WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
-                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(ACTION_UP),
-                                          WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
-                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(AllOf(WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+                                                         WithCoords(0.0f, 0.0f),
+                                                         WithPressure(0.0f)))));
 
     // Another move.
     args.clear();
@@ -377,7 +402,8 @@
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
                               expectedCoords, expectedCursorPosition,
-                              WithRelativeMotion(10.0f, 20.0f)))));
+                              WithPositiveAxis(AMOTION_EVENT_AXIS_RELATIVE_X),
+                              WithPositiveAxis(AMOTION_EVENT_AXIS_RELATIVE_Y)))));
 }
 
 TEST_F(CursorInputMapperUnitTest, PopulateDeviceInfoReturnsScaledRangeInNavigationMode) {
@@ -411,64 +437,40 @@
     args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithEventTime(ARBITRARY_TIME), WithDeviceId(DEVICE_ID),
-                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
-                                          WithEdgeFlags(0), WithPolicyFlags(0),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithPointerCount(1), WithPointerId(0, 0),
-                                          WithToolType(ToolType::MOUSE), WithCoords(0.0f, 0.0f),
-                                          WithPressure(1.0f),
-                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
-                                                        TRACKBALL_MOVEMENT_THRESHOLD),
-                                          WithDownTime(ARBITRARY_TIME))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithEventTime(ARBITRARY_TIME), WithDeviceId(DEVICE_ID),
-                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
-                                          WithEdgeFlags(0), WithPolicyFlags(0),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithPointerCount(1), WithPointerId(0, 0),
-                                          WithToolType(ToolType::MOUSE), WithCoords(0.0f, 0.0f),
-                                          WithPressure(1.0f),
-                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
-                                                        TRACKBALL_MOVEMENT_THRESHOLD),
-                                          WithDownTime(ARBITRARY_TIME)))));
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithEventTime(ARBITRARY_TIME), WithDeviceId(DEVICE_ID),
+                              WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0), WithEdgeFlags(0),
+                              WithPolicyFlags(0),
+                              WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                              WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPointerCount(1),
+                              WithPointerId(0, 0), WithToolType(ToolType::MOUSE),
+                              WithCoords(0.0f, 0.0f), WithPressure(1.0f),
+                              WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                            TRACKBALL_MOVEMENT_THRESHOLD),
+                              WithDownTime(ARBITRARY_TIME)))));
     args.clear();
 
     // Button release.  Should have same down time.
     args += process(ARBITRARY_TIME + 1, EV_KEY, BTN_MOUSE, 0);
     args += process(ARBITRARY_TIME + 1, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithEventTime(ARBITRARY_TIME + 1),
-                                          WithDeviceId(DEVICE_ID),
-                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
-                                          WithEdgeFlags(0), WithPolicyFlags(0),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
-                                          WithButtonState(0), WithPointerCount(1),
-                                          WithPointerId(0, 0), WithToolType(ToolType::MOUSE),
-                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f),
-                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
-                                                        TRACKBALL_MOVEMENT_THRESHOLD),
-                                          WithDownTime(ARBITRARY_TIME))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithEventTime(ARBITRARY_TIME + 1),
-                                          WithDeviceId(DEVICE_ID),
-                                          WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0),
-                                          WithEdgeFlags(0), WithPolicyFlags(0),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
-                                          WithButtonState(0), WithPointerCount(1),
-                                          WithPointerId(0, 0), WithToolType(ToolType::MOUSE),
-                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f),
-                                          WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
-                                                        TRACKBALL_MOVEMENT_THRESHOLD),
-                                          WithDownTime(ARBITRARY_TIME)))));
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithEventTime(ARBITRARY_TIME + 1), WithDeviceId(DEVICE_ID),
+                              WithSource(AINPUT_SOURCE_TRACKBALL), WithFlags(0), WithEdgeFlags(0),
+                              WithPolicyFlags(0),
+                              WithMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON),
+                              WithButtonState(0), WithPointerCount(1), WithPointerId(0, 0),
+                              WithToolType(ToolType::MOUSE), WithCoords(0.0f, 0.0f),
+                              WithPressure(0.0f),
+                              WithPrecision(TRACKBALL_MOVEMENT_THRESHOLD,
+                                            TRACKBALL_MOVEMENT_THRESHOLD),
+                              WithDownTime(ARBITRARY_TIME)))));
 }
 
 TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleIndependentXYUpdates) {
@@ -482,9 +484,8 @@
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                              WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f),
-                              WithPressure(0.0f)))));
+                        AllOf(WithMotionAction(ACTION_MOVE), WithPressure(0.0f),
+                              WithPositiveAxis(AXIS_X), WithZeroAxis(AXIS_Y)))));
     args.clear();
 
     // Motion in Y but not X.
@@ -492,9 +493,8 @@
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                              WithCoords(0.0f, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD),
-                              WithPressure(0.0f)))));
+                        AllOf(WithMotionAction(ACTION_MOVE), WithPressure(0.0f),
+                              WithZeroAxis(AXIS_X), WithNegativeAxis(AXIS_Y)))));
     args.clear();
 }
 
@@ -508,24 +508,22 @@
     args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
     args.clear();
 
     // Button release.
     args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 0);
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
 }
 
 TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleCombinedXYAndButtonUpdates) {
@@ -540,16 +538,12 @@
     args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 1);
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-                                                     -2.0f / TRACKBALL_MOVEMENT_THRESHOLD),
-                                          WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithCoords(1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-                                                     -2.0f / TRACKBALL_MOVEMENT_THRESHOLD),
-                                          WithPressure(1.0f)))));
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(AllOf(WithPositiveAxis(AXIS_X),
+                                                         WithNegativeAxis(AXIS_Y),
+                                                         WithPressure(1.0f)))));
     args.clear();
 
     // Move X, Y a bit while pressed.
@@ -558,22 +552,19 @@
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                              WithCoords(2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
-                                         1.0f / TRACKBALL_MOVEMENT_THRESHOLD),
-                              WithPressure(1.0f)))));
+                        AllOf(WithMotionAction(ACTION_MOVE), WithPressure(1.0f),
+                              WithPositiveAxis(AXIS_X), WithPositiveAxis(AXIS_Y)))));
     args.clear();
 
     // Release Button.
     args += process(ARBITRARY_TIME, EV_KEY, BTN_MOUSE, 0);
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
     args.clear();
 }
 
@@ -586,14 +577,16 @@
             .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation90)));
     mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1,  1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, -1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1,  1));
+    constexpr auto X = AXIS_X;
+    constexpr auto Y = AXIS_Y;
+    ASSERT_NO_FATAL_FAILURE(testRotation( 0,  1, AllOf(WithZeroAxis(X),     WithPositiveAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1,  1, AllOf(WithPositiveAxis(X), WithPositiveAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1,  0, AllOf(WithPositiveAxis(X), WithZeroAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1, -1, AllOf(WithPositiveAxis(X), WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 0, -1, AllOf(WithZeroAxis(X),     WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1, -1, AllOf(WithNegativeAxis(X), WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1,  0, AllOf(WithNegativeAxis(X), WithZeroAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1,  1, AllOf(WithNegativeAxis(X), WithPositiveAxis(Y))));
 }
 
 TEST_F(CursorInputMapperUnitTest, ProcessShouldRotateMotionsWhenNotOrientationAware) {
@@ -604,54 +597,56 @@
             .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation0)));
     mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1,  1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, -1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1,  1));
+    constexpr auto X = AXIS_X;
+    constexpr auto Y = AXIS_Y;
+    ASSERT_NO_FATAL_FAILURE(testRotation( 0,  1, AllOf(WithZeroAxis(X),     WithPositiveAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1,  1, AllOf(WithPositiveAxis(X), WithPositiveAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1,  0, AllOf(WithPositiveAxis(X), WithZeroAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1, -1, AllOf(WithPositiveAxis(X), WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 0, -1, AllOf(WithZeroAxis(X),     WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1, -1, AllOf(WithNegativeAxis(X), WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1,  0, AllOf(WithNegativeAxis(X), WithZeroAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1,  1, AllOf(WithNegativeAxis(X), WithPositiveAxis(Y))));
 
     EXPECT_CALL((*mDevice), getAssociatedViewport)
             .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation90)));
     std::list<NotifyArgs> args =
             mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                  InputReaderConfiguration::Change::DISPLAY_INFO);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1, -1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1,  1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1,  1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1, -1));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 0,  1, AllOf(WithNegativeAxis(X), WithZeroAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1,  1, AllOf(WithNegativeAxis(X), WithPositiveAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1,  0, AllOf(WithZeroAxis(X),     WithPositiveAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1, -1, AllOf(WithPositiveAxis(X), WithPositiveAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 0, -1, AllOf(WithPositiveAxis(X), WithZeroAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1, -1, AllOf(WithPositiveAxis(X), WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1,  0, AllOf(WithZeroAxis(X),     WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1,  1, AllOf(WithNegativeAxis(X), WithNegativeAxis(Y))));
 
     EXPECT_CALL((*mDevice), getAssociatedViewport)
             .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation180)));
     args = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                 InputReaderConfiguration::Change::DISPLAY_INFO);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1, -1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1, -1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1,  1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1,  1, -1));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 0,  1, AllOf(WithZeroAxis(X),     WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1,  1, AllOf(WithNegativeAxis(X), WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1,  0, AllOf(WithNegativeAxis(X), WithZeroAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1, -1, AllOf(WithNegativeAxis(X), WithPositiveAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 0, -1, AllOf(WithZeroAxis(X),     WithPositiveAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1, -1, AllOf(WithPositiveAxis(X), WithPositiveAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1,  0, AllOf(WithPositiveAxis(X), WithZeroAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1,  1, AllOf(WithPositiveAxis(X), WithNegativeAxis(Y))));
 
     EXPECT_CALL((*mDevice), getAssociatedViewport)
             .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation270)));
     args = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                 InputReaderConfiguration::Change::DISPLAY_INFO);
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  0,  0, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1, -1, -1, -1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0, -1, -1,  0));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1, -1, -1,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  0,  1));
-    ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1,  1,  1));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 0,  1, AllOf(WithPositiveAxis(X), WithZeroAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1,  1, AllOf(WithPositiveAxis(X), WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1,  0, AllOf(WithZeroAxis(X),     WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 1, -1, AllOf(WithNegativeAxis(X), WithNegativeAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation( 0, -1, AllOf(WithNegativeAxis(X), WithZeroAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1, -1, AllOf(WithNegativeAxis(X), WithPositiveAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1,  0, AllOf(WithZeroAxis(X),     WithPositiveAxis(Y))));
+    ASSERT_NO_FATAL_FAILURE(testRotation(-1,  1, AllOf(WithPositiveAxis(X), WithPositiveAxis(Y))));
 }
 
 TEST_F(CursorInputMapperUnitTest, PopulateDeviceInfoReturnsRangeFromPolicy) {
@@ -742,30 +737,22 @@
     args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1);
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0.0f, 0.0f),
+                              WithPressure(1.0f)))));
     args.clear();
     args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 0);
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
-                                          WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
-                                          WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
-                                          WithPressure(0.0f)))));
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithButtonState(0), WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
     args.clear();
 
     // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
@@ -774,49 +761,41 @@
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                    AllOf(WithMotionAction(ACTION_DOWN),
                                           WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
-                                                          AMOTION_EVENT_BUTTON_TERTIARY),
-                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                                                          AMOTION_EVENT_BUTTON_TERTIARY))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
-                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+                                    AllOf(WithMotionAction(BUTTON_PRESS),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                    AllOf(WithMotionAction(BUTTON_PRESS),
                                           WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
-                                                          AMOTION_EVENT_BUTTON_TERTIARY),
-                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+                                                          AMOTION_EVENT_BUTTON_TERTIARY)))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
     args.clear();
 
     args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 0);
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
-                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
-                                          WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_MOVE))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
+                              WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
     args.clear();
 
     args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 0);
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
-                                          WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithButtonState(0),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithButtonState(0),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithButtonState(0), WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
 }
 
 class CursorInputMapperButtonKeyTest
@@ -838,11 +817,11 @@
                 ElementsAre(VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN),
                                                              WithKeyCode(expectedKeyCode))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                    AllOf(WithMotionAction(HOVER_MOVE),
                                           WithButtonState(expectedButtonState),
                                           WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                    AllOf(WithMotionAction(BUTTON_PRESS),
                                           WithButtonState(expectedButtonState),
                                           WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
     args.clear();
@@ -851,13 +830,11 @@
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
-                                          WithPressure(0.0f))),
+                                    AllOf(WithMotionAction(BUTTON_RELEASE), WithButtonState(0),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                          WithButtonState(0), WithCoords(0.0f, 0.0f),
-                                          WithPressure(0.0f))),
+                                    AllOf(WithMotionAction(HOVER_MOVE), WithButtonState(0),
+                                          WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
                             VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP),
                                                              WithKeyCode(expectedKeyCode)))));
 }
@@ -881,8 +858,7 @@
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE), WithMotionAction(HOVER_MOVE),
                               WithCoords(0.0f, 0.0f), WithPressure(0.0f), WithSize(0.0f),
                               WithTouchDimensions(0.0f, 0.0f), WithToolDimensions(0.0f, 0.0f),
                               WithOrientation(0.0f), WithDistance(0.0f)))));
@@ -897,13 +873,11 @@
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
 
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
                                           WithScroll(1.0f, 1.0f)))));
+    EXPECT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithSource(AINPUT_SOURCE_MOUSE))));
 }
 
 TEST_F(CursorInputMapperUnitTest, ProcessHighResScroll) {
@@ -920,13 +894,11 @@
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
 
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
                                           WithScroll(0.5f, 0.5f)))));
+    EXPECT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithSource(AINPUT_SOURCE_MOUSE))));
 }
 
 TEST_F(CursorInputMapperUnitTest, HighResScrollIgnoresRegularScroll) {
@@ -945,13 +917,11 @@
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
 
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
                                           WithScroll(0.5f, 0.5f)))));
+    EXPECT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithSource(AINPUT_SOURCE_MOUSE))));
 }
 
 TEST_F(CursorInputMapperUnitTest, ProcessReversedVerticalScroll) {
@@ -966,13 +936,11 @@
     // Reversed vertical scrolling only affects the y-axis, expect it to be -1.0f to indicate the
     // inverted scroll direction.
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
                                           WithScroll(1.0f, -1.0f)))));
+    EXPECT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithSource(AINPUT_SOURCE_MOUSE))));
 }
 
 TEST_F(CursorInputMapperUnitTest, ProcessHighResReversedVerticalScroll) {
@@ -990,13 +958,11 @@
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
 
     EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
                                           WithScroll(0.5f, -0.5f)))));
+    EXPECT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithSource(AINPUT_SOURCE_MOUSE))));
 }
 
 /**
@@ -1005,10 +971,6 @@
  */
 TEST_F(CursorInputMapperUnitTest, PointerCaptureDisablesVelocityProcessing) {
     mPropertyMap.addProperty("cursor.mode", "pointer");
-    const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f,
-                                               /*highThreshold=*/100.f, /*acceleration=*/10.f);
-    mReaderConfiguration.pointerVelocityControlParameters = testParams;
-    mFakePolicy->setVelocityControlParams(testParams);
     createMapper();
 
     NotifyMotionArgs motionArgs;
@@ -1020,8 +982,7 @@
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))));
+                        AllOf(WithSource(AINPUT_SOURCE_MOUSE), WithMotionAction(HOVER_MOVE)))));
     motionArgs = std::get<NotifyMotionArgs>(args.front());
     const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
     const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
@@ -1039,12 +1000,7 @@
     EXPECT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                         AllOf(WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
-                              WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))));
-    motionArgs = std::get<NotifyMotionArgs>(args.front());
-    const float relX2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float relY2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    ASSERT_EQ(10, relX2);
-    ASSERT_EQ(20, relY2);
+                              WithMotionAction(ACTION_MOVE), WithRelativeMotion(10, 20)))));
 }
 
 TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdNoAssociatedViewport) {
@@ -1067,54 +1023,12 @@
     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                              WithSource(AINPUT_SOURCE_MOUSE),
+                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
                               WithDisplayId(ui::LogicalDisplayId::INVALID),
                               WithCoords(0.0f, 0.0f)))));
 }
 
-// TODO(b/320433834): De-duplicate the test cases once the flag is removed.
-class CursorInputMapperUnitTestWithNewBallistics : public CursorInputMapperUnitTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_new_mouse_pointer_ballistics(true);
-        CursorInputMapperUnitTestBase::SetUp();
-    }
-};
-
-TEST_F(CursorInputMapperUnitTestWithNewBallistics, PointerCaptureDisablesVelocityProcessing) {
-    mPropertyMap.addProperty("cursor.mode", "pointer");
-    createMapper();
-
-    NotifyMotionArgs motionArgs;
-    std::list<NotifyArgs> args;
-
-    // Move and verify scale is applied.
-    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
-    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    motionArgs = std::get<NotifyMotionArgs>(args.front());
-    const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    ASSERT_GT(relX, 10);
-    ASSERT_GT(relY, 20);
-    args.clear();
-
-    // Enable Pointer Capture
-    setPointerCapture(true);
-
-    // Move and verify scale is not applied.
-    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
-    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    motionArgs = std::get<NotifyMotionArgs>(args.front());
-    const float relX2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float relY2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    ASSERT_EQ(10, relX2);
-    ASSERT_EQ(20, relY2);
-}
-
-TEST_F(CursorInputMapperUnitTestWithNewBallistics, ConfigureAccelerationWithAssociatedViewport) {
+TEST_F(CursorInputMapperUnitTest, ConfigureAccelerationWithAssociatedViewport) {
     mPropertyMap.addProperty("cursor.mode", "pointer");
     DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0);
     mReaderConfiguration.setDisplayViewports({primaryViewport});
@@ -1149,7 +1063,7 @@
                                                                 WithRelativeMotion(10, 20)))));
 }
 
-TEST_F(CursorInputMapperUnitTestWithNewBallistics, ConfigureAccelerationOnDisplayChange) {
+TEST_F(CursorInputMapperUnitTest, ConfigureAccelerationOnDisplayChange) {
     mPropertyMap.addProperty("cursor.mode", "pointer");
     DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0);
     mReaderConfiguration.setDisplayViewports({primaryViewport});
@@ -1186,72 +1100,6 @@
                               WithRelativeMotion(10, 20)))));
 }
 
-TEST_F(CursorInputMapperUnitTestWithNewBallistics, ProcessRegularScroll) {
-    createMapper();
-
-    std::list<NotifyArgs> args;
-    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
-    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
-                                          WithScroll(1.0f, 1.0f)))));
-}
-
-TEST_F(CursorInputMapperUnitTestWithNewBallistics, ProcessHighResScroll) {
-    vd_flags::high_resolution_scroll(true);
-    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
-            .WillRepeatedly(Return(true));
-    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
-            .WillRepeatedly(Return(true));
-    createMapper();
-
-    std::list<NotifyArgs> args;
-    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
-    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
-                                          WithScroll(0.5f, 0.5f)))));
-}
-
-TEST_F(CursorInputMapperUnitTestWithNewBallistics, HighResScrollIgnoresRegularScroll) {
-    vd_flags::high_resolution_scroll(true);
-    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
-            .WillRepeatedly(Return(true));
-    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
-            .WillRepeatedly(Return(true));
-    createMapper();
-
-    std::list<NotifyArgs> args;
-    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
-    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60);
-    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
-    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
-                                          WithScroll(0.5f, 0.5f)))));
-}
-
 namespace {
 
 // Minimum timestamp separation between subsequent input events from a Bluetooth device.
@@ -1279,8 +1127,7 @@
     argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(argsList,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                              WithEventTime(expectedEventTime)))));
+                        AllOf(WithMotionAction(HOVER_MOVE), WithEventTime(expectedEventTime)))));
     argsList.clear();
 
     // Process several events that come in quick succession, according to their timestamps.
@@ -1294,7 +1141,7 @@
         argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
         EXPECT_THAT(argsList,
                     ElementsAre(VariantWith<NotifyMotionArgs>(
-                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                            AllOf(WithMotionAction(HOVER_MOVE),
                                   WithEventTime(expectedEventTime)))));
         argsList.clear();
     }
@@ -1310,8 +1157,7 @@
     argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(argsList,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                              WithEventTime(expectedEventTime)))));
+                        AllOf(WithMotionAction(HOVER_MOVE), WithEventTime(expectedEventTime)))));
     argsList.clear();
 
     // Process several events with the same timestamp from the kernel.
@@ -1325,7 +1171,7 @@
         argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
         EXPECT_THAT(argsList,
                     ElementsAre(VariantWith<NotifyMotionArgs>(
-                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                            AllOf(WithMotionAction(HOVER_MOVE),
                                   WithEventTime(expectedEventTime)))));
         argsList.clear();
     }
@@ -1338,8 +1184,7 @@
         argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
         EXPECT_THAT(argsList,
                     ElementsAre(VariantWith<NotifyMotionArgs>(
-                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                  WithEventTime(cappedEventTime)))));
+                            AllOf(WithMotionAction(HOVER_MOVE), WithEventTime(cappedEventTime)))));
         argsList.clear();
     }
 }
@@ -1355,8 +1200,7 @@
     argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(argsList,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                              WithEventTime(expectedEventTime)))));
+                        AllOf(WithMotionAction(HOVER_MOVE), WithEventTime(expectedEventTime)))));
     argsList.clear();
 
     // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
@@ -1368,8 +1212,7 @@
     argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
     EXPECT_THAT(argsList,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                              WithEventTime(expectedEventTime)))));
+                        AllOf(WithMotionAction(HOVER_MOVE), WithEventTime(expectedEventTime)))));
     argsList.clear();
 }
 
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index 943de6e..e72c440 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -650,4 +650,25 @@
     }
 }
 
+bool FakeEventHub::setKernelWakeEnabled(int32_t deviceId, bool enabled) {
+    Device* device = getDevice(deviceId);
+    if (device == nullptr) {
+        return false;
+    }
+    mKernelWakeup.emplace(deviceId, enabled);
+    return true;
+}
+
+bool FakeEventHub::fakeReadKernelWakeup(int32_t deviceId) const {
+    Device* device = getDevice(deviceId);
+    if (device == nullptr) {
+        return false;
+    }
+    auto it = mKernelWakeup.find(deviceId);
+    if (it == mKernelWakeup.end()) {
+        return false;
+    }
+    return it->second;
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index 2dfbb23..143b93b 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -94,6 +94,8 @@
     // Simulates a device light intensities, from light id to light intensities map.
     std::unordered_map<int32_t /* lightId */, std::unordered_map<LightColor, int32_t>>
             mLightIntensities;
+    // fake sysfs node path and value.
+    std::unordered_map<int32_t /*deviceId*/, bool /* wakeupNode*/> mKernelWakeup;
 
 public:
     static constexpr int32_t DEFAULT_BATTERY = 1;
@@ -158,6 +160,8 @@
     void setMtSlotValues(int32_t deviceId, int32_t axis, const std::vector<int32_t>& values);
     base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
                                                        size_t slotCount) const override;
+    bool setKernelWakeEnabled(int32_t deviceId, bool enabled) override;
+    bool fakeReadKernelWakeup(int32_t deviceId) const;
 
 private:
     Device* getDevice(int32_t deviceId) const;
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index f373cac..7c5f350 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -217,7 +217,6 @@
 }
 
 void FakeInputReaderPolicy::setVelocityControlParams(const VelocityControlParameters& params) {
-    mConfig.pointerVelocityControlParameters = params;
     mConfig.wheelVelocityControlParameters = params;
 }
 
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 6c8b65c..ee3b2a2 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -28,7 +28,6 @@
 #include <MultiTouchInputMapper.h>
 #include <NotifyArgsBuilders.h>
 #include <PeripheralController.h>
-#include <SensorInputMapper.h>
 #include <SingleTouchInputMapper.h>
 #include <TestEventMatchers.h>
 #include <TestInputListener.h>
@@ -36,6 +35,7 @@
 #include <UinputDevice.h>
 #include <android-base/thread_annotations.h>
 #include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <ftl/enum.h>
 #include <gtest/gtest.h>
 #include <ui/Rotation.h>
@@ -1392,6 +1392,20 @@
     ASSERT_EQ(mReader->getLightColor(deviceId, /*lightId=*/1), LIGHT_BRIGHTNESS);
 }
 
+TEST_F(InputReaderTest, SetPowerWakeUp) {
+    ASSERT_NO_FATAL_FAILURE(addDevice(1, "1st", InputDeviceClass::KEYBOARD, nullptr));
+    ASSERT_NO_FATAL_FAILURE(addDevice(2, "2nd", InputDeviceClass::KEYBOARD, nullptr));
+    ASSERT_NO_FATAL_FAILURE(addDevice(3, "3rd", InputDeviceClass::KEYBOARD, nullptr));
+
+    ASSERT_EQ(mFakeEventHub->fakeReadKernelWakeup(1), false);
+
+    ASSERT_TRUE(mFakeEventHub->setKernelWakeEnabled(2, true));
+    ASSERT_EQ(mFakeEventHub->fakeReadKernelWakeup(2), true);
+
+    ASSERT_TRUE(mFakeEventHub->setKernelWakeEnabled(3, false));
+    ASSERT_EQ(mFakeEventHub->fakeReadKernelWakeup(3), false);
+}
+
 // --- InputReaderIntegrationTest ---
 
 // These tests create and interact with the InputReader only through its interface.
@@ -3017,159 +3031,6 @@
     mapper.assertProcessWasCalled();
 }
 
-// --- SensorInputMapperTest ---
-
-class SensorInputMapperTest : public InputMapperTest {
-protected:
-    static const int32_t ACCEL_RAW_MIN;
-    static const int32_t ACCEL_RAW_MAX;
-    static const int32_t ACCEL_RAW_FUZZ;
-    static const int32_t ACCEL_RAW_FLAT;
-    static const int32_t ACCEL_RAW_RESOLUTION;
-
-    static const int32_t GYRO_RAW_MIN;
-    static const int32_t GYRO_RAW_MAX;
-    static const int32_t GYRO_RAW_FUZZ;
-    static const int32_t GYRO_RAW_FLAT;
-    static const int32_t GYRO_RAW_RESOLUTION;
-
-    static const float GRAVITY_MS2_UNIT;
-    static const float DEGREE_RADIAN_UNIT;
-
-    void prepareAccelAxes();
-    void prepareGyroAxes();
-    void setAccelProperties();
-    void setGyroProperties();
-    void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::SENSOR); }
-};
-
-const int32_t SensorInputMapperTest::ACCEL_RAW_MIN = -32768;
-const int32_t SensorInputMapperTest::ACCEL_RAW_MAX = 32768;
-const int32_t SensorInputMapperTest::ACCEL_RAW_FUZZ = 16;
-const int32_t SensorInputMapperTest::ACCEL_RAW_FLAT = 0;
-const int32_t SensorInputMapperTest::ACCEL_RAW_RESOLUTION = 8192;
-
-const int32_t SensorInputMapperTest::GYRO_RAW_MIN = -2097152;
-const int32_t SensorInputMapperTest::GYRO_RAW_MAX = 2097152;
-const int32_t SensorInputMapperTest::GYRO_RAW_FUZZ = 16;
-const int32_t SensorInputMapperTest::GYRO_RAW_FLAT = 0;
-const int32_t SensorInputMapperTest::GYRO_RAW_RESOLUTION = 1024;
-
-const float SensorInputMapperTest::GRAVITY_MS2_UNIT = 9.80665f;
-const float SensorInputMapperTest::DEGREE_RADIAN_UNIT = 0.0174533f;
-
-void SensorInputMapperTest::prepareAccelAxes() {
-    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ,
-                                   ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION);
-    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ,
-                                   ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION);
-    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Z, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ,
-                                   ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION);
-}
-
-void SensorInputMapperTest::prepareGyroAxes() {
-    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RX, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ,
-                                   GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION);
-    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RY, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ,
-                                   GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION);
-    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RZ, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ,
-                                   GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION);
-}
-
-void SensorInputMapperTest::setAccelProperties() {
-    mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 0, InputDeviceSensorType::ACCELEROMETER,
-                                 /* sensorDataIndex */ 0);
-    mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 1, InputDeviceSensorType::ACCELEROMETER,
-                                 /* sensorDataIndex */ 1);
-    mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 2, InputDeviceSensorType::ACCELEROMETER,
-                                 /* sensorDataIndex */ 2);
-    mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP);
-    addConfigurationProperty("sensor.accelerometer.reportingMode", "0");
-    addConfigurationProperty("sensor.accelerometer.maxDelay", "100000");
-    addConfigurationProperty("sensor.accelerometer.minDelay", "5000");
-    addConfigurationProperty("sensor.accelerometer.power", "1.5");
-}
-
-void SensorInputMapperTest::setGyroProperties() {
-    mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 3, InputDeviceSensorType::GYROSCOPE,
-                                 /* sensorDataIndex */ 0);
-    mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 4, InputDeviceSensorType::GYROSCOPE,
-                                 /* sensorDataIndex */ 1);
-    mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 5, InputDeviceSensorType::GYROSCOPE,
-                                 /* sensorDataIndex */ 2);
-    mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP);
-    addConfigurationProperty("sensor.gyroscope.reportingMode", "0");
-    addConfigurationProperty("sensor.gyroscope.maxDelay", "100000");
-    addConfigurationProperty("sensor.gyroscope.minDelay", "5000");
-    addConfigurationProperty("sensor.gyroscope.power", "0.8");
-}
-
-TEST_F(SensorInputMapperTest, GetSources) {
-    SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
-
-    ASSERT_EQ(static_cast<uint32_t>(AINPUT_SOURCE_SENSOR), mapper.getSources());
-}
-
-TEST_F(SensorInputMapperTest, ProcessAccelerometerSensor) {
-    setAccelProperties();
-    prepareAccelAxes();
-    SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
-
-    ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::ACCELEROMETER,
-                                    std::chrono::microseconds(10000),
-                                    std::chrono::microseconds(0)));
-    ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(EVENTHUB_ID));
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, 20000);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, -20000);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Z, 40000);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_TIMESTAMP, 1000);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-
-    NotifySensorArgs args;
-    std::vector<float> values = {20000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT,
-                                 -20000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT,
-                                 40000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT};
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySensorWasCalled(&args));
-    ASSERT_EQ(args.source, AINPUT_SOURCE_SENSOR);
-    ASSERT_EQ(args.deviceId, DEVICE_ID);
-    ASSERT_EQ(args.sensorType, InputDeviceSensorType::ACCELEROMETER);
-    ASSERT_EQ(args.accuracy, InputDeviceSensorAccuracy::ACCURACY_HIGH);
-    ASSERT_EQ(args.hwTimestamp, ARBITRARY_TIME);
-    ASSERT_EQ(args.values, values);
-    mapper.flushSensor(InputDeviceSensorType::ACCELEROMETER);
-}
-
-TEST_F(SensorInputMapperTest, ProcessGyroscopeSensor) {
-    setGyroProperties();
-    prepareGyroAxes();
-    SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
-
-    ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::GYROSCOPE,
-                                    std::chrono::microseconds(10000),
-                                    std::chrono::microseconds(0)));
-    ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(EVENTHUB_ID));
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RX, 20000);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RY, -20000);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RZ, 40000);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_TIMESTAMP, 1000);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-
-    NotifySensorArgs args;
-    std::vector<float> values = {20000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT,
-                                 -20000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT,
-                                 40000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT};
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySensorWasCalled(&args));
-    ASSERT_EQ(args.source, AINPUT_SOURCE_SENSOR);
-    ASSERT_EQ(args.deviceId, DEVICE_ID);
-    ASSERT_EQ(args.sensorType, InputDeviceSensorType::GYROSCOPE);
-    ASSERT_EQ(args.accuracy, InputDeviceSensorAccuracy::ACCURACY_HIGH);
-    ASSERT_EQ(args.hwTimestamp, ARBITRARY_TIME);
-    ASSERT_EQ(args.values, values);
-    mapper.flushSensor(InputDeviceSensorType::GYROSCOPE);
-}
-
 // --- KeyboardInputMapperTest ---
 
 class KeyboardInputMapperTest : public InputMapperTest {
@@ -4005,6 +3866,44 @@
     ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_KEEP_TOUCH_MODE, args.flags);
 }
 
+TEST_F_WITH_FLAGS(KeyboardInputMapperTest, WakeBehavior_AlphabeticKeyboard,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                      enable_alphabetic_keyboard_wake))) {
+    // For internal alphabetic devices, keys will trigger wake on key down.
+
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_PLAYPAUSE, 0, AKEYCODE_MEDIA_PLAY_PAUSE, 0);
+
+    KeyboardInputMapper& mapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
+
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_A, 1);
+    NotifyKeyArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_A, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_HOME, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 1);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+
+    process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAYPAUSE, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+}
+
 /**
  * When there is more than one KeyboardInputMapper for an InputDevice, each mapper should produce
  * events that use the shared keyboard source across all mappers. This is to ensure that each
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index a43e4e4..25e2b45 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -180,6 +180,7 @@
     MOCK_METHOD(status_t, enableDevice, (int32_t deviceId), (override));
     MOCK_METHOD(status_t, disableDevice, (int32_t deviceId), (override));
     MOCK_METHOD(void, sysfsNodeChanged, (const std::string& sysfsNodePath), (override));
+    MOCK_METHOD(bool, setKernelWakeEnabled, (int32_t deviceId, bool enabled), (override));
 };
 
 class MockPointerChoreographerPolicyInterface : public PointerChoreographerPolicyInterface {
diff --git a/services/inputflinger/tests/SensorInputMapper_test.cpp b/services/inputflinger/tests/SensorInputMapper_test.cpp
new file mode 100644
index 0000000..01814a6
--- /dev/null
+++ b/services/inputflinger/tests/SensorInputMapper_test.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SensorInputMapper.h"
+
+#include <vector>
+
+#include <EventHub.h>
+#include <NotifyArgs.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <input/InputDevice.h>
+#include <linux/input-event-codes.h>
+
+#include "InputMapperTest.h"
+
+namespace android {
+
+class SensorInputMapperTest : public InputMapperTest {
+protected:
+    static const int32_t ACCEL_RAW_MIN;
+    static const int32_t ACCEL_RAW_MAX;
+    static const int32_t ACCEL_RAW_FUZZ;
+    static const int32_t ACCEL_RAW_FLAT;
+    static const int32_t ACCEL_RAW_RESOLUTION;
+
+    static const int32_t GYRO_RAW_MIN;
+    static const int32_t GYRO_RAW_MAX;
+    static const int32_t GYRO_RAW_FUZZ;
+    static const int32_t GYRO_RAW_FLAT;
+    static const int32_t GYRO_RAW_RESOLUTION;
+
+    static const float GRAVITY_MS2_UNIT;
+    static const float DEGREE_RADIAN_UNIT;
+
+    void prepareAccelAxes();
+    void prepareGyroAxes();
+    void setAccelProperties();
+    void setGyroProperties();
+    void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::SENSOR); }
+};
+
+const int32_t SensorInputMapperTest::ACCEL_RAW_MIN = -32768;
+const int32_t SensorInputMapperTest::ACCEL_RAW_MAX = 32768;
+const int32_t SensorInputMapperTest::ACCEL_RAW_FUZZ = 16;
+const int32_t SensorInputMapperTest::ACCEL_RAW_FLAT = 0;
+const int32_t SensorInputMapperTest::ACCEL_RAW_RESOLUTION = 8192;
+
+const int32_t SensorInputMapperTest::GYRO_RAW_MIN = -2097152;
+const int32_t SensorInputMapperTest::GYRO_RAW_MAX = 2097152;
+const int32_t SensorInputMapperTest::GYRO_RAW_FUZZ = 16;
+const int32_t SensorInputMapperTest::GYRO_RAW_FLAT = 0;
+const int32_t SensorInputMapperTest::GYRO_RAW_RESOLUTION = 1024;
+
+const float SensorInputMapperTest::GRAVITY_MS2_UNIT = 9.80665f;
+const float SensorInputMapperTest::DEGREE_RADIAN_UNIT = 0.0174533f;
+
+void SensorInputMapperTest::prepareAccelAxes() {
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ,
+                                   ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ,
+                                   ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Z, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ,
+                                   ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION);
+}
+
+void SensorInputMapperTest::prepareGyroAxes() {
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RX, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ,
+                                   GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RY, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ,
+                                   GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RZ, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ,
+                                   GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION);
+}
+
+void SensorInputMapperTest::setAccelProperties() {
+    mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 0, InputDeviceSensorType::ACCELEROMETER,
+                                 /* sensorDataIndex */ 0);
+    mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 1, InputDeviceSensorType::ACCELEROMETER,
+                                 /* sensorDataIndex */ 1);
+    mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 2, InputDeviceSensorType::ACCELEROMETER,
+                                 /* sensorDataIndex */ 2);
+    mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP);
+    addConfigurationProperty("sensor.accelerometer.reportingMode", "0");
+    addConfigurationProperty("sensor.accelerometer.maxDelay", "100000");
+    addConfigurationProperty("sensor.accelerometer.minDelay", "5000");
+    addConfigurationProperty("sensor.accelerometer.power", "1.5");
+}
+
+void SensorInputMapperTest::setGyroProperties() {
+    mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 3, InputDeviceSensorType::GYROSCOPE,
+                                 /* sensorDataIndex */ 0);
+    mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 4, InputDeviceSensorType::GYROSCOPE,
+                                 /* sensorDataIndex */ 1);
+    mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 5, InputDeviceSensorType::GYROSCOPE,
+                                 /* sensorDataIndex */ 2);
+    mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP);
+    addConfigurationProperty("sensor.gyroscope.reportingMode", "0");
+    addConfigurationProperty("sensor.gyroscope.maxDelay", "100000");
+    addConfigurationProperty("sensor.gyroscope.minDelay", "5000");
+    addConfigurationProperty("sensor.gyroscope.power", "0.8");
+}
+
+TEST_F(SensorInputMapperTest, GetSources) {
+    SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
+
+    ASSERT_EQ(static_cast<uint32_t>(AINPUT_SOURCE_SENSOR), mapper.getSources());
+}
+
+TEST_F(SensorInputMapperTest, ProcessAccelerometerSensor) {
+    setAccelProperties();
+    prepareAccelAxes();
+    SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
+
+    ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::ACCELEROMETER,
+                                    std::chrono::microseconds(10000),
+                                    std::chrono::microseconds(0)));
+    ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(EVENTHUB_ID));
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, 20000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, -20000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Z, 40000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_TIMESTAMP, 1000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+
+    NotifySensorArgs args;
+    std::vector<float> values = {20000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT,
+                                 -20000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT,
+                                 40000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT};
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySensorWasCalled(&args));
+    ASSERT_EQ(args.source, AINPUT_SOURCE_SENSOR);
+    ASSERT_EQ(args.deviceId, DEVICE_ID);
+    ASSERT_EQ(args.sensorType, InputDeviceSensorType::ACCELEROMETER);
+    ASSERT_EQ(args.accuracy, InputDeviceSensorAccuracy::ACCURACY_HIGH);
+    ASSERT_EQ(args.hwTimestamp, ARBITRARY_TIME);
+    ASSERT_EQ(args.values, values);
+    mapper.flushSensor(InputDeviceSensorType::ACCELEROMETER);
+}
+
+TEST_F(SensorInputMapperTest, ProcessGyroscopeSensor) {
+    setGyroProperties();
+    prepareGyroAxes();
+    SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
+
+    ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::GYROSCOPE,
+                                    std::chrono::microseconds(10000),
+                                    std::chrono::microseconds(0)));
+    ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(EVENTHUB_ID));
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RX, 20000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RY, -20000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_RZ, 40000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_TIMESTAMP, 1000);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+
+    NotifySensorArgs args;
+    std::vector<float> values = {20000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT,
+                                 -20000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT,
+                                 40000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT};
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySensorWasCalled(&args));
+    ASSERT_EQ(args.source, AINPUT_SOURCE_SENSOR);
+    ASSERT_EQ(args.deviceId, DEVICE_ID);
+    ASSERT_EQ(args.sensorType, InputDeviceSensorType::GYROSCOPE);
+    ASSERT_EQ(args.accuracy, InputDeviceSensorAccuracy::ACCURACY_HIGH);
+    ASSERT_EQ(args.hwTimestamp, ARBITRARY_TIME);
+    ASSERT_EQ(args.values, values);
+    mapper.flushSensor(InputDeviceSensorType::GYROSCOPE);
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index f58d8fd..7078e49 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -108,20 +108,33 @@
     using is_gtest_matcher = void;
     explicit WithMotionActionMatcher(int32_t action) : mAction(action) {}
 
-    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
-        bool matches = mAction == args.action;
-        if (args.action == AMOTION_EVENT_ACTION_CANCEL) {
-            matches &= (args.flags & AMOTION_EVENT_FLAG_CANCELED) != 0;
+    bool MatchAndExplain(const NotifyMotionArgs& args,
+                         testing::MatchResultListener* listener) const {
+        if (mAction != args.action) {
+            *listener << "expected " << MotionEvent::actionToString(mAction) << ", but got "
+                      << MotionEvent::actionToString(args.action);
+            return false;
         }
-        return matches;
+        if (args.action == AMOTION_EVENT_ACTION_CANCEL &&
+            (args.flags & AMOTION_EVENT_FLAG_CANCELED) == 0) {
+            *listener << "event with CANCEL action is missing FLAG_CANCELED";
+            return false;
+        }
+        return true;
     }
 
-    bool MatchAndExplain(const MotionEvent& event, std::ostream*) const {
-        bool matches = mAction == event.getAction();
-        if (event.getAction() == AMOTION_EVENT_ACTION_CANCEL) {
-            matches &= (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
+    bool MatchAndExplain(const MotionEvent& event, testing::MatchResultListener* listener) const {
+        if (mAction != event.getAction()) {
+            *listener << "expected " << MotionEvent::actionToString(mAction) << ", but got "
+                      << MotionEvent::actionToString(event.getAction());
+            return false;
         }
-        return matches;
+        if (event.getAction() == AMOTION_EVENT_ACTION_CANCEL &&
+            (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) == 0) {
+            *listener << "event with CANCEL action is missing FLAG_CANCELED";
+            return false;
+        }
+        return true;
     }
 
     void DescribeTo(std::ostream* os) const {
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index 5442a65..64f3c27 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -171,6 +171,10 @@
 
     void notifyMouseCursorFadedOnTyping() override { reader->notifyMouseCursorFadedOnTyping(); }
 
+    bool setKernelWakeEnabled(int32_t deviceId, bool enabled) override {
+        return reader->setKernelWakeEnabled(deviceId, enabled);
+    }
+
 private:
     std::unique_ptr<InputReaderInterface> reader;
 };
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index fa8270a..60c676d 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -269,6 +269,9 @@
     status_t enableDevice(int32_t deviceId) override { return mFdp->ConsumeIntegral<status_t>(); }
     status_t disableDevice(int32_t deviceId) override { return mFdp->ConsumeIntegral<status_t>(); }
     void sysfsNodeChanged(const std::string& sysfsNodePath) override {}
+    bool setKernelWakeEnabled(int32_t deviceId, bool enabled) override {
+        return mFdp->ConsumeBool();
+    }
 };
 
 class FuzzInputReaderPolicy : public InputReaderPolicyInterface {
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index 1589c99..f5c4fc5 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -35,6 +35,7 @@
 using aidl::android::hardware::power::Mode;
 using aidl::android::hardware::power::SessionConfig;
 using aidl::android::hardware::power::SessionTag;
+using aidl::android::hardware::power::SupportInfo;
 using android::binder::Status;
 
 using namespace android;
@@ -65,6 +66,7 @@
                 (int32_t tgid, int32_t uid, ChannelConfig* _aidl_return), (override));
     MOCK_METHOD(ndk::ScopedAStatus, closeSessionChannel, (int32_t tgid, int32_t uid), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSupportInfo, (SupportInfo * _aidl_return), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
     MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index d500ae8..f1c79c1 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -148,6 +148,46 @@
     },
 }
 
+// libsurfaceflinger_backend_{headers|sources} are a step towards pulling out
+// the "backend" sources to clean up the dependency graph between
+// CompositionEngine and SurfaceFlinger. Completing the cleanup would require
+// moving the headers in particular so that the dependency can strictly be a
+// DAG. There would certainly be additional cleanups: VirtualDisplaySurface.cpp
+// and FrameBufferSurface.cpp likely belong in CompositionEngine for example.
+cc_library_headers {
+    name: "libsurfaceflinger_backend_headers",
+    export_include_dirs: ["."],
+    static_libs: ["libserviceutils"],
+    export_static_lib_headers: ["libserviceutils"],
+
+    shared_libs: [
+        "android.hardware.configstore-utils",
+        "android.hardware.configstore@1.0",
+        "android.hardware.configstore@1.1",
+        "libbinder_ndk",
+    ],
+    export_shared_lib_headers: [
+        "android.hardware.configstore-utils",
+        "android.hardware.configstore@1.0",
+        "android.hardware.configstore@1.1",
+        "libbinder_ndk",
+    ],
+}
+
+filegroup {
+    name: "libsurfaceflinger_backend_sources",
+    srcs: [
+        "DisplayHardware/AidlComposerHal.cpp",
+        "DisplayHardware/ComposerHal.cpp",
+        "DisplayHardware/FramebufferSurface.cpp",
+        "DisplayHardware/HWC2.cpp",
+        "DisplayHardware/HWComposer.cpp",
+        "DisplayHardware/HidlComposerHal.cpp",
+        "DisplayHardware/PowerAdvisor.cpp",
+        "DisplayHardware/VirtualDisplaySurface.cpp",
+    ],
+}
+
 cc_library_headers {
     name: "libsurfaceflinger_headers",
     export_include_dirs: ["."],
@@ -158,20 +198,13 @@
 filegroup {
     name: "libsurfaceflinger_sources",
     srcs: [
+        ":libsurfaceflinger_backend_sources",
         "BackgroundExecutor.cpp",
         "Client.cpp",
         "ClientCache.cpp",
         "Display/DisplayModeController.cpp",
         "Display/DisplaySnapshot.cpp",
         "DisplayDevice.cpp",
-        "DisplayHardware/AidlComposerHal.cpp",
-        "DisplayHardware/ComposerHal.cpp",
-        "DisplayHardware/FramebufferSurface.cpp",
-        "DisplayHardware/HWC2.cpp",
-        "DisplayHardware/HWComposer.cpp",
-        "DisplayHardware/HidlComposerHal.cpp",
-        "DisplayHardware/PowerAdvisor.cpp",
-        "DisplayHardware/VirtualDisplaySurface.cpp",
         "DisplayRenderArea.cpp",
         "Effects/Daltonizer.cpp",
         "FrontEnd/LayerCreationArgs.cpp",
@@ -251,7 +284,6 @@
     ],
     static_libs: [
         "android.frameworks.displayservice@1.0",
-        "libc++fs",
         "libdisplayservicehidl",
         "libserviceutils",
     ],
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index b4ac9ba..7095b9d 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -59,7 +59,7 @@
         "android.hardware.graphics.composer@2.3-command-buffer",
         "android.hardware.graphics.composer@2.4-command-buffer",
         "android.hardware.graphics.composer3-command-buffer",
-        "libsurfaceflinger_headers",
+        "libsurfaceflinger_backend_headers",
     ],
 }
 
@@ -141,6 +141,8 @@
     ],
     srcs: [
         ":libcompositionengine_sources",
+        ":libsurfaceflinger_backend_mock_sources",
+        ":libsurfaceflinger_backend_sources",
         "tests/planner/CachedSetTest.cpp",
         "tests/planner/FlattenerTest.cpp",
         "tests/planner/LayerStateTest.cpp",
@@ -151,14 +153,14 @@
         "tests/DisplayTest.cpp",
         "tests/HwcAsyncWorkerTest.cpp",
         "tests/HwcBufferCacheTest.cpp",
-        "tests/MockHWC2.cpp",
-        "tests/MockHWComposer.cpp",
-        "tests/MockPowerAdvisor.cpp",
         "tests/OutputLayerTest.cpp",
         "tests/OutputTest.cpp",
         "tests/ProjectionSpaceTest.cpp",
         "tests/RenderSurfaceTest.cpp",
     ],
+    header_libs: [
+        "libsurfaceflinger_backend_mock_headers",
+    ],
     static_libs: [
         "libcompositionengine_mocks",
         "libgui_mocks",
@@ -167,6 +169,7 @@
         "libgtest",
     ],
     shared_libs: [
+        "libbinder_ndk",
         // For some reason, libvulkan isn't picked up from librenderengine
         // Probably ASAN related?
         "libvulkan",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 14a8fd6..8a91a07 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -19,6 +19,7 @@
 #include <cstdint>
 
 #include <android/gui/CachingHint.h>
+#include <gui/DisplayLuts.h>
 #include <gui/HdrMetadata.h>
 #include <math/mat4.h>
 #include <ui/BlurRegion.h>
@@ -219,6 +220,9 @@
     float desiredHdrSdrRatio = 1.f;
 
     gui::CachingHint cachingHint = gui::CachingHint::Enabled;
+
+    std::shared_ptr<gui::DisplayLuts> luts;
+
     virtual ~LayerFECompositionState();
 
     // Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index 191d475..556aa24 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -329,6 +329,8 @@
     virtual bool isPowerHintSessionGpuReportingEnabled() = 0;
     virtual void cacheClientCompositionRequests(uint32_t cacheSize) = 0;
     virtual bool canPredictCompositionStrategy(const CompositionRefreshArgs&) = 0;
+    virtual const aidl::android::hardware::graphics::composer3::OverlayProperties*
+    getOverlaySupport() = 0;
 };
 
 } // namespace compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index dcfe21a..80c5124 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -93,7 +93,10 @@
     // transform, if needed.
     virtual void updateCompositionState(
             bool includeGeometry, bool forceClientComposition,
-            ui::Transform::RotationFlags internalDisplayRotationFlags) = 0;
+            ui::Transform::RotationFlags internalDisplayRotationFlags,
+            const std::optional<std::vector<
+                    std::optional<aidl::android::hardware::graphics::composer3::LutProperties>>>
+                    properties) = 0;
 
     // Writes the geometry state to the HWC, or does nothing if this layer does
     // not use the HWC. If includeGeometry is false, the geometry state can be
@@ -129,8 +132,10 @@
     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;
+    virtual void applyDeviceLayerLut(
+            ndk::ScopedFileDescriptor,
+            std::vector<std::pair<
+                    int, aidl::android::hardware::graphics::composer3::LutProperties>>) = 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 a39abb4..d8466ff 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -103,6 +103,8 @@
     DisplayId mId;
     bool mIsDisconnected = false;
     Hwc2::PowerAdvisor* mPowerAdvisor = nullptr;
+    const aidl::android::hardware::graphics::composer3::OverlayProperties* getOverlaySupport()
+            override;
 };
 
 // This template factory function standardizes the implementation details of the
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 9990a74..69e1efc 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -164,6 +164,8 @@
     bool mustRecompose() const;
 
     const std::string& getNamePlusId() const { return mNamePlusId; }
+    const aidl::android::hardware::graphics::composer3::OverlayProperties* getOverlaySupport()
+            override;
 
 private:
     void dirtyEntireOutput();
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index 354a441..0c7e4dd 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -31,6 +31,8 @@
 
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 
+using aidl::android::hardware::graphics::composer3::LutProperties;
+
 namespace android::compositionengine {
 
 struct LayerFECompositionState;
@@ -48,7 +50,9 @@
     void uncacheBuffers(const std::vector<uint64_t>& bufferIdsToUncache) override;
 
     void updateCompositionState(bool includeGeometry, bool forceClientComposition,
-                                ui::Transform::RotationFlags) override;
+                                ui::Transform::RotationFlags,
+                                const std::optional<std::vector<std::optional<LutProperties>>>
+                                        properties = std::nullopt) override;
     void writeStateToHWC(bool includeGeometry, bool skipLayer, uint32_t z, bool zIsOverridden,
                          bool isPeekingThrough) override;
     void writeCursorPositionToHWC() const override;
@@ -60,8 +64,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;
+    void applyDeviceLayerLut(ndk::ScopedFileDescriptor,
+                             std::vector<std::pair<int, LutProperties>>) override;
     bool needsFiltering() const override;
     std::optional<LayerFE::LayerSettings> getOverrideCompositionSettings() const override;
 
@@ -92,10 +96,13 @@
     void writeCompositionTypeToHWC(HWC2::Layer*,
                                    aidl::android::hardware::graphics::composer3::Composition,
                                    bool isPeekingThrough, bool skipLayer);
+    void writeLutToHWC(HWC2::Layer*, const LayerFECompositionState&);
     void detectDisallowedCompositionTypeChange(
             aidl::android::hardware::graphics::composer3::Composition from,
             aidl::android::hardware::graphics::composer3::Composition to) const;
     bool isClientCompositionForced(bool isPeekingThrough) const;
+    void updateLuts(std::shared_ptr<gui::DisplayLuts> luts,
+                    const std::optional<std::vector<std::optional<LutProperties>>>& properties);
 };
 
 // This template factory function standardizes the implementation details of the
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
index 6c419da..28216a4 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
@@ -18,6 +18,7 @@
 
 #include <compositionengine/ProjectionSpace.h>
 #include <compositionengine/impl/HwcBufferCache.h>
+#include <gui/DisplayLuts.h>
 #include <renderengine/ExternalTexture.h>
 #include <ui/FloatRect.h>
 #include <ui/GraphicTypes.h>
@@ -151,6 +152,9 @@
 
         // True when this layer was skipped as part of SF-side layer caching.
         bool layerSkipped = false;
+
+        // lut information
+        std::shared_ptr<gui::DisplayLuts> luts;
     };
 
     // The HWC state is optional, and is only set up if there is any potential
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index d5bf2b5..33cdc54 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -140,6 +140,8 @@
     MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine));
     MOCK_METHOD(bool, isPowerHintSessionEnabled, ());
     MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, ());
+    MOCK_METHOD((const aidl::android::hardware::graphics::composer3::OverlayProperties*),
+                getOverlaySupport, ());
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
index 48c2f9c..12f2094 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
@@ -43,7 +43,10 @@
     MOCK_CONST_METHOD0(getState, const impl::OutputLayerCompositionState&());
     MOCK_METHOD0(editState, impl::OutputLayerCompositionState&());
 
-    MOCK_METHOD3(updateCompositionState, void(bool, bool, ui::Transform::RotationFlags));
+    MOCK_METHOD(void, updateCompositionState,
+                (bool, bool, ui::Transform::RotationFlags,
+                 (const std::optional<std::vector<std::optional<
+                          aidl::android::hardware::graphics::composer3::LutProperties>>>)));
     MOCK_METHOD5(writeStateToHWC, void(bool, bool, uint32_t, bool, bool));
     MOCK_CONST_METHOD0(writeCursorPositionToHWC, void());
 
@@ -57,8 +60,9 @@
     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));
+                (ndk::ScopedFileDescriptor,
+                 (std::vector<std::pair<
+                          int, aidl::android::hardware::graphics::composer3::LutProperties>>)));
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index b0164b7..1825065 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -370,8 +370,8 @@
 
         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()));
+                layer->applyDeviceLayerLut(ndk::ScopedFileDescriptor(mapperIt->second.release()),
+                                           lutsIt->second);
             }
         }
     }
@@ -457,6 +457,11 @@
     mPowerAdvisor->setRequiresRenderEngine(mId, requiresRenderEngine);
 }
 
+const aidl::android::hardware::graphics::composer3::OverlayProperties*
+Display::getOverlaySupport() {
+    return &getCompositionEngine().getHwComposer().getOverlaySupport();
+}
+
 void Display::finishFrame(GpuCompositionResult&& result) {
     // We only need to actually compose the display if:
     // 1) It is being handled by hardware composer, which may need this to
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 22ab3d9..bb45613 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -813,11 +813,14 @@
     mLayerRequestingBackgroundBlur = findLayerRequestingBackgroundComposition();
     bool forceClientComposition = mLayerRequestingBackgroundBlur != nullptr;
 
+    auto* properties = getOverlaySupport();
+
     for (auto* layer : getOutputLayersOrderedByZ()) {
         layer->updateCompositionState(refreshArgs.updatingGeometryThisFrame,
                                       refreshArgs.devOptForceClientComposition ||
                                               forceClientComposition,
-                                      refreshArgs.internalDisplayRotationFlags);
+                                      refreshArgs.internalDisplayRotationFlags,
+                                      properties ? properties->lutProperties : std::nullopt);
 
         if (mLayerRequestingBackgroundBlur == layer) {
             forceClientComposition = false;
@@ -1678,6 +1681,10 @@
     editState().treat170mAsSrgb = enable;
 }
 
+const aidl::android::hardware::graphics::composer3::OverlayProperties* Output::getOverlaySupport() {
+    return nullptr;
+}
+
 bool Output::canPredictCompositionStrategy(const CompositionRefreshArgs& refreshArgs) {
     uint64_t lastOutputLayerHash = getState().lastOutputLayerHash;
     uint64_t outputLayerHash = getState().outputLayerHash;
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 2d46dc0..934909d 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -38,7 +38,7 @@
 #pragma clang diagnostic pop // ignored "-Wconversion"
 
 using aidl::android::hardware::graphics::composer3::Composition;
-using aidl::android::hardware::graphics::composer3::LutProperties;
+using aidl::android::hardware::graphics::composer3::Luts;
 
 namespace android::compositionengine {
 
@@ -285,9 +285,50 @@
     return transform.getOrientation();
 }
 
+void OutputLayer::updateLuts(
+        std::shared_ptr<gui::DisplayLuts> layerFEStateLut,
+        const std::optional<std::vector<std::optional<LutProperties>>>& properties) {
+    auto& state = editState();
+
+    if (!properties) {
+        // GPU composition if no Hwc Luts
+        state.forceClientComposition = true;
+        return;
+    }
+
+    std::vector<LutProperties> hwcLutProperties;
+    for (auto& p : *properties) {
+        if (p) {
+            hwcLutProperties.emplace_back(*p);
+        }
+    }
+
+    for (const auto& inputLut : layerFEStateLut->lutProperties) {
+        bool foundInHwcLuts = false;
+        for (const auto& hwcLut : hwcLutProperties) {
+            if (static_cast<int32_t>(hwcLut.dimension) ==
+                        static_cast<int32_t>(inputLut.dimension) &&
+                hwcLut.size == inputLut.size &&
+                std::find(hwcLut.samplingKeys.begin(), hwcLut.samplingKeys.end(),
+                          static_cast<LutProperties::SamplingKey>(inputLut.samplingKey)) !=
+                        hwcLut.samplingKeys.end()) {
+                foundInHwcLuts = true;
+                break;
+            }
+        }
+        // if any lut properties of layerFEStateLut can not be found in hwcLutProperties,
+        // GPU composition instead
+        if (!foundInHwcLuts) {
+            state.forceClientComposition = true;
+            return;
+        }
+    }
+}
+
 void OutputLayer::updateCompositionState(
         bool includeGeometry, bool forceClientComposition,
-        ui::Transform::RotationFlags internalDisplayRotationFlags) {
+        ui::Transform::RotationFlags internalDisplayRotationFlags,
+        const std::optional<std::vector<std::optional<LutProperties>>> properties) {
     const auto* layerFEState = getLayerFE().getCompositionState();
     if (!layerFEState) {
         return;
@@ -368,6 +409,11 @@
         state.whitePointNits = layerBrightnessNits;
     }
 
+    const auto& layerFEStateLut = layerFEState->luts;
+    if (layerFEStateLut) {
+        updateLuts(layerFEStateLut, properties);
+    }
+
     // These are evaluated every frame as they can potentially change at any
     // time.
     if (layerFEState->forceClientComposition || !profile.isDataspaceSupported(state.dataspace) ||
@@ -420,6 +466,8 @@
     writeCompositionTypeToHWC(hwcLayer.get(), requestedCompositionType, isPeekingThrough,
                               skipLayer);
 
+    writeLutToHWC(hwcLayer.get(), *outputIndependentState);
+
     if (requestedCompositionType == Composition::SOLID_COLOR) {
         writeSolidColorStateToHWC(hwcLayer.get(), *outputIndependentState);
     }
@@ -513,6 +561,40 @@
     }
 }
 
+void OutputLayer::writeLutToHWC(HWC2::Layer* hwcLayer,
+                                const LayerFECompositionState& outputIndependentState) {
+    if (!outputIndependentState.luts) {
+        return;
+    }
+    auto& lutFileDescriptor = outputIndependentState.luts->getLutFileDescriptor();
+    auto lutOffsets = outputIndependentState.luts->offsets;
+    auto& lutProperties = outputIndependentState.luts->lutProperties;
+
+    std::vector<LutProperties> aidlProperties;
+    aidlProperties.reserve(lutProperties.size());
+    for (size_t i = 0; i < lutOffsets.size(); i++) {
+        LutProperties properties;
+        properties.dimension = static_cast<LutProperties::Dimension>(lutProperties[i].dimension);
+        properties.size = lutProperties[i].size;
+        properties.samplingKeys = {
+                static_cast<LutProperties::SamplingKey>(lutProperties[i].samplingKey)};
+        aidlProperties.emplace_back(properties);
+    }
+
+    Luts luts;
+    luts.pfd = ndk::ScopedFileDescriptor(dup(lutFileDescriptor.get()));
+    luts.offsets = lutOffsets;
+    luts.lutProperties = std::move(aidlProperties);
+
+    switch (auto error = hwcLayer->setLuts(luts)) {
+        case hal::Error::NONE:
+            break;
+        default:
+            ALOGE("[%s] Failed to set Luts: %s (%d)", getLayerFE().getDebugName(),
+                  to_string(error).c_str(), static_cast<int32_t>(error));
+    }
+}
+
 void OutputLayer::writeOutputDependentPerFrameStateToHWC(HWC2::Layer* hwcLayer) {
     const auto& outputDependentState = getState();
 
@@ -849,10 +931,29 @@
     }
 }
 
-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
+void OutputLayer::applyDeviceLayerLut(
+        ndk::ScopedFileDescriptor lutFileDescriptor,
+        std::vector<std::pair<int, LutProperties>> lutOffsetsAndProperties) {
+    auto& state = editState();
+    LOG_FATAL_IF(!state.hwc);
+    auto& hwcState = *state.hwc;
+    std::vector<int32_t> offsets;
+    std::vector<int32_t> dimensions;
+    std::vector<int32_t> sizes;
+    std::vector<int32_t> samplingKeys;
+    for (const auto& [offset, properties] : lutOffsetsAndProperties) {
+        // The Lut(s) that comes back through CommandResultPayload should be
+        // only one sampling key.
+        if (properties.samplingKeys.size() == 1) {
+            offsets.emplace_back(offset);
+            dimensions.emplace_back(static_cast<int32_t>(properties.dimension));
+            sizes.emplace_back(static_cast<int32_t>(properties.size));
+            samplingKeys.emplace_back(static_cast<int32_t>(properties.samplingKeys[0]));
+        }
+    }
+    hwcState.luts = std::make_shared<gui::DisplayLuts>(base::unique_fd(lutFileDescriptor.release()),
+                                                       std::move(offsets), std::move(dimensions),
+                                                       std::move(sizes), std::move(samplingKeys));
 }
 
 bool OutputLayer::needsFiltering() const {
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index 48ebc32..3e0c390 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -26,9 +26,8 @@
 #include <gtest/gtest.h>
 #include <renderengine/mock/RenderEngine.h>
 
-#include "MockHWComposer.h"
 #include "TimeStats/TimeStats.h"
-#include "gmock/gmock.h"
+#include "mock/DisplayHardware/MockHWComposer.h"
 
 using namespace com::android::graphics::surfaceflinger;
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 9c0e62c..416001e 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -36,10 +36,10 @@
 #include <ui/Rect.h>
 #include <ui/StaticDisplayInfo.h>
 
-#include "MockHWC2.h"
-#include "MockHWComposer.h"
-#include "MockPowerAdvisor.h"
 #include "ftl/future.h"
+#include "mock/DisplayHardware/MockHWC2.h"
+#include "mock/DisplayHardware/MockHWComposer.h"
+#include "mock/DisplayHardware/MockPowerAdvisor.h"
 
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.cpp b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.cpp
deleted file mode 100644
index 0baa79d..0000000
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2019 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 "MockHWC2.h"
-
-namespace android::HWC2 {
-
-// This will go away once HWC2::Layer is moved into the "backend" library
-Layer::~Layer() = default;
-
-namespace mock {
-
-// The Google Mock documentation recommends explicit non-header instantiations
-// for better compile time performance.
-Layer::Layer() = default;
-Layer::~Layer() = default;
-
-} // namespace mock
-} // namespace android::HWC2
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
deleted file mode 100644
index eb6e677..0000000
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2019 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 <gmock/gmock.h>
-#include <ui/Fence.h>
-#include <ui/FloatRect.h>
-#include <ui/GraphicBuffer.h>
-#include <ui/Rect.h>
-#include <ui/Region.h>
-#include <ui/Transform.h>
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#pragma clang diagnostic ignored "-Wextra"
-
-#include <ui/GraphicTypes.h>
-#include "DisplayHardware/HWC2.h"
-
-#include <aidl/android/hardware/graphics/composer3/Composition.h>
-#include <aidl/android/hardware/graphics/composer3/Lut.h>
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
-
-namespace android {
-namespace HWC2 {
-namespace mock {
-
-namespace hal = android::hardware::graphics::composer::hal;
-
-using Error = hal::Error;
-
-class Layer : public HWC2::Layer {
-public:
-    Layer();
-    ~Layer() override;
-
-    MOCK_CONST_METHOD0(getId, hal::HWLayerId());
-
-    MOCK_METHOD2(setCursorPosition, Error(int32_t, int32_t));
-    MOCK_METHOD3(setBuffer,
-                 Error(uint32_t, const android::sp<android::GraphicBuffer>&,
-                       const android::sp<android::Fence>&));
-    MOCK_METHOD2(setBufferSlotsToClear, Error(const std::vector<uint32_t>&, uint32_t));
-    MOCK_METHOD1(setSurfaceDamage, Error(const android::Region&));
-    MOCK_METHOD1(setBlendMode, Error(hal::BlendMode));
-    MOCK_METHOD1(setColor, Error(aidl::android::hardware::graphics::composer3::Color));
-    MOCK_METHOD1(setCompositionType,
-                 Error(aidl::android::hardware::graphics::composer3::Composition));
-    MOCK_METHOD1(setDataspace, Error(android::ui::Dataspace));
-    MOCK_METHOD2(setPerFrameMetadata, Error(const int32_t, const android::HdrMetadata&));
-    MOCK_METHOD1(setDisplayFrame, Error(const android::Rect&));
-    MOCK_METHOD1(setPlaneAlpha, Error(float));
-    MOCK_METHOD1(setSidebandStream, Error(const native_handle_t*));
-    MOCK_METHOD1(setSourceCrop, Error(const android::FloatRect&));
-    MOCK_METHOD1(setTransform, Error(hal::Transform));
-    MOCK_METHOD1(setVisibleRegion, Error(const android::Region&));
-    MOCK_METHOD1(setZOrder, Error(uint32_t));
-
-    MOCK_METHOD1(setColorTransform, Error(const android::mat4&));
-    MOCK_METHOD3(setLayerGenericMetadata,
-                 Error(const std::string&, bool, const std::vector<uint8_t>&));
-    MOCK_METHOD1(setBrightness, Error(float));
-    MOCK_METHOD1(setBlockingRegion, Error(const android::Region&));
-    MOCK_METHOD(Error, setLuts, (std::vector<aidl::android::hardware::graphics::composer3::Lut>&));
-};
-
-} // namespace mock
-} // namespace HWC2
-} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
deleted file mode 100644
index 5c55ce7..0000000
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 2018 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 <compositionengine/Output.h>
-#include <gmock/gmock.h>
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#pragma clang diagnostic ignored "-Wextra"
-
-#include "DisplayHardware/HWComposer.h"
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
-
-namespace android {
-namespace mock {
-
-namespace hal = android::hardware::graphics::composer::hal;
-
-class HWComposer : public android::HWComposer {
-public:
-    HWComposer();
-    ~HWComposer() override;
-
-    MOCK_METHOD1(setCallback, void(HWC2::ComposerCallback&));
-    MOCK_CONST_METHOD3(getDisplayIdentificationData,
-                       bool(hal::HWDisplayId, uint8_t*, DisplayIdentificationData*));
-    MOCK_CONST_METHOD1(hasCapability,
-                       bool(aidl::android::hardware::graphics::composer3::Capability));
-    MOCK_CONST_METHOD2(hasDisplayCapability,
-                       bool(HalDisplayId,
-                            aidl::android::hardware::graphics::composer3::DisplayCapability));
-
-    MOCK_CONST_METHOD0(getMaxVirtualDisplayCount, size_t());
-    MOCK_CONST_METHOD0(getMaxVirtualDisplayDimension, size_t());
-    MOCK_METHOD3(allocateVirtualDisplay, bool(HalVirtualDisplayId, ui::Size, ui::PixelFormat*));
-    MOCK_METHOD3(allocatePhysicalDisplay,
-                 void(hal::HWDisplayId, PhysicalDisplayId, std::optional<ui::Size>));
-
-    MOCK_METHOD1(createLayer, std::shared_ptr<HWC2::Layer>(HalDisplayId));
-    MOCK_METHOD(status_t, getDeviceCompositionChanges,
-                (HalDisplayId, bool, std::optional<std::chrono::steady_clock::time_point>, nsecs_t,
-                 Fps, std::optional<android::HWComposer::DeviceRequestedChanges>*));
-    MOCK_METHOD(status_t, setClientTarget,
-                (HalDisplayId, uint32_t, const sp<Fence>&, const sp<GraphicBuffer>&, ui::Dataspace,
-                 float),
-                (override));
-    MOCK_METHOD2(presentAndGetReleaseFences,
-                 status_t(HalDisplayId, std::optional<std::chrono::steady_clock::time_point>));
-    MOCK_METHOD(status_t, executeCommands, (HalDisplayId));
-    MOCK_METHOD2(setPowerMode, status_t(PhysicalDisplayId, hal::PowerMode));
-    MOCK_METHOD2(setActiveConfig, status_t(HalDisplayId, size_t));
-    MOCK_METHOD2(setColorTransform, status_t(HalDisplayId, const mat4&));
-    MOCK_METHOD1(disconnectDisplay, void(HalDisplayId));
-    MOCK_CONST_METHOD1(hasDeviceComposition, bool(const std::optional<DisplayId>&));
-    MOCK_CONST_METHOD1(getPresentFence, sp<Fence>(HalDisplayId));
-    MOCK_METHOD(nsecs_t, getPresentTimestamp, (PhysicalDisplayId), (const, override));
-    MOCK_CONST_METHOD2(getLayerReleaseFence, sp<Fence>(HalDisplayId, HWC2::Layer*));
-    MOCK_METHOD3(setOutputBuffer,
-                 status_t(HalVirtualDisplayId, const sp<Fence>&, const sp<GraphicBuffer>&));
-    MOCK_METHOD1(clearReleaseFences, void(HalDisplayId));
-    MOCK_METHOD2(getHdrCapabilities, status_t(HalDisplayId, HdrCapabilities*));
-    MOCK_CONST_METHOD1(getSupportedPerFrameMetadata, int32_t(HalDisplayId));
-    MOCK_CONST_METHOD2(getRenderIntents,
-                       std::vector<ui::RenderIntent>(HalDisplayId, ui::ColorMode));
-    MOCK_METHOD2(getDataspaceSaturationMatrix, mat4(HalDisplayId, ui::Dataspace));
-    MOCK_METHOD4(getDisplayedContentSamplingAttributes,
-                 status_t(HalDisplayId, ui::PixelFormat*, ui::Dataspace*, uint8_t*));
-    MOCK_METHOD4(setDisplayContentSamplingEnabled, status_t(HalDisplayId, bool, uint8_t, uint64_t));
-    MOCK_METHOD4(getDisplayedContentSample,
-                 status_t(HalDisplayId, uint64_t, uint64_t, DisplayedFrameStats*));
-    MOCK_METHOD(ftl::Future<status_t>, setDisplayBrightness,
-                (PhysicalDisplayId, float, float, const Hwc2::Composer::DisplayBrightnessOptions&),
-                (override));
-    MOCK_METHOD2(getDisplayBrightnessSupport, status_t(PhysicalDisplayId, bool*));
-
-    MOCK_METHOD2(onHotplug,
-                 std::optional<DisplayIdentificationInfo>(hal::HWDisplayId, hal::Connection));
-    MOCK_CONST_METHOD0(updatesDeviceProductInfoOnHotplugReconnect, bool());
-    MOCK_METHOD(std::optional<PhysicalDisplayId>, onVsync, (hal::HWDisplayId, int64_t));
-    MOCK_METHOD2(setVsyncEnabled, void(PhysicalDisplayId, hal::Vsync));
-    MOCK_CONST_METHOD1(isConnected, bool(PhysicalDisplayId));
-    MOCK_CONST_METHOD2(getModes,
-                       std::vector<HWComposer::HWCDisplayMode>(PhysicalDisplayId, int32_t));
-    MOCK_CONST_METHOD1(getActiveMode, ftl::Expected<hal::HWConfigId, status_t>(PhysicalDisplayId));
-    MOCK_CONST_METHOD1(getColorModes, std::vector<ui::ColorMode>(PhysicalDisplayId));
-    MOCK_METHOD3(setActiveColorMode, status_t(PhysicalDisplayId, ui::ColorMode, ui::RenderIntent));
-    MOCK_CONST_METHOD0(isUsingVrComposer, bool());
-    MOCK_CONST_METHOD1(getDisplayConnectionType, ui::DisplayConnectionType(PhysicalDisplayId));
-    MOCK_CONST_METHOD1(isVsyncPeriodSwitchSupported, bool(PhysicalDisplayId));
-    MOCK_CONST_METHOD1(getDisplayVsyncPeriod, ftl::Expected<nsecs_t, status_t>(PhysicalDisplayId));
-    MOCK_METHOD4(setActiveModeWithConstraints,
-                 status_t(PhysicalDisplayId, hal::HWConfigId,
-                          const hal::VsyncPeriodChangeConstraints&,
-                          hal::VsyncPeriodChangeTimeline*));
-    MOCK_METHOD2(setBootDisplayMode, status_t(PhysicalDisplayId, hal::HWConfigId));
-    MOCK_METHOD1(clearBootDisplayMode, status_t(PhysicalDisplayId));
-    MOCK_METHOD1(getPreferredBootDisplayMode, std::optional<hal::HWConfigId>(PhysicalDisplayId));
-    MOCK_METHOD0(getBootDisplayModeSupport, bool());
-    MOCK_CONST_METHOD0(
-            getHdrConversionCapabilities,
-            std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>());
-    MOCK_METHOD2(setHdrConversionStrategy,
-                 status_t(aidl::android::hardware::graphics::common::HdrConversionStrategy,
-                          aidl::android::hardware::graphics::common::Hdr*));
-    MOCK_METHOD2(setAutoLowLatencyMode, status_t(PhysicalDisplayId, bool));
-    MOCK_METHOD(status_t, getSupportedContentTypes,
-                (PhysicalDisplayId, std::vector<hal::ContentType>*), (const, override));
-    MOCK_METHOD2(setContentType, status_t(PhysicalDisplayId, hal::ContentType));
-    MOCK_CONST_METHOD0(getSupportedLayerGenericMetadata,
-                       const std::unordered_map<std::string, bool>&());
-
-    MOCK_CONST_METHOD1(dump, void(std::string&));
-    MOCK_CONST_METHOD1(dumpOverlayProperties, void(std::string&));
-    MOCK_CONST_METHOD0(getComposer, android::Hwc2::Composer*());
-
-    MOCK_METHOD(hal::HWDisplayId, getPrimaryHwcDisplayId, (), (const, override));
-    MOCK_METHOD(PhysicalDisplayId, getPrimaryDisplayId, (), (const, override));
-    MOCK_METHOD(bool, isHeadless, (), (const, override));
-
-    MOCK_METHOD(std::optional<PhysicalDisplayId>, toPhysicalDisplayId, (hal::HWDisplayId),
-                (const, override));
-    MOCK_METHOD(std::optional<hal::HWDisplayId>, fromPhysicalDisplayId, (PhysicalDisplayId),
-                (const, override));
-    MOCK_METHOD2(getDisplayDecorationSupport,
-                 status_t(PhysicalDisplayId,
-                          std::optional<aidl::android::hardware::graphics::common::
-                                                DisplayDecorationSupport>* support));
-    MOCK_METHOD2(setIdleTimerEnabled, status_t(PhysicalDisplayId, std::chrono::milliseconds));
-    MOCK_METHOD(bool, hasDisplayIdleTimerCapability, (PhysicalDisplayId), (const, override));
-    MOCK_METHOD(Hwc2::AidlTransform, getPhysicalDisplayOrientation, (PhysicalDisplayId),
-                (const, override));
-    MOCK_METHOD(bool, getValidateSkipped, (HalDisplayId), (const, override));
-    MOCK_METHOD(const aidl::android::hardware::graphics::composer3::OverlayProperties&,
-                getOverlaySupport, (), (const, override));
-    MOCK_METHOD(status_t, setRefreshRateChangedCallbackDebugEnabled, (PhysicalDisplayId, bool));
-    MOCK_METHOD(status_t, notifyExpectedPresent, (PhysicalDisplayId, TimePoint, Fps));
-    MOCK_METHOD((HWC2::Display::LutFileDescriptorMapper&), getLutFileDescriptorMapper, (), ());
-};
-
-} // namespace mock
-} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.cpp b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.cpp
deleted file mode 100644
index 85b9403..0000000
--- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.cpp
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2019 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 "MockPowerAdvisor.h"
-
-namespace android {
-namespace Hwc2 {
-
-// This will go away once PowerAdvisor is moved into the "backend" library
-PowerAdvisor::~PowerAdvisor() = default;
-
-namespace mock {
-
-// The Google Mock documentation recommends explicit non-header instantiations
-// for better compile time performance.
-PowerAdvisor::PowerAdvisor() = default;
-PowerAdvisor::~PowerAdvisor() = default;
-
-} // namespace mock
-} // namespace Hwc2
-} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
deleted file mode 100644
index ed2ffa9..0000000
--- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2019 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 <gmock/gmock.h>
-
-#include "DisplayHardware/PowerAdvisor.h"
-
-namespace android {
-namespace Hwc2 {
-namespace mock {
-
-class PowerAdvisor : public android::Hwc2::PowerAdvisor {
-public:
-    PowerAdvisor();
-    ~PowerAdvisor() override;
-
-    MOCK_METHOD(void, init, (), (override));
-    MOCK_METHOD(void, onBootFinished, (), (override));
-    MOCK_METHOD(void, setExpensiveRenderingExpected, (DisplayId displayId, bool expected),
-                (override));
-    MOCK_METHOD(bool, isUsingExpensiveRendering, (), (override));
-    MOCK_METHOD(void, notifyCpuLoadUp, (), (override));
-    MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
-    MOCK_METHOD(bool, usePowerHintSession, (), (override));
-    MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
-    MOCK_METHOD(bool, supportsGpuReporting, (), (override));
-    MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
-    MOCK_METHOD(void, reportActualWorkDuration, (), (override));
-    MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
-    MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
-    MOCK_METHOD(void, setGpuStartTime, (DisplayId displayId, TimePoint startTime), (override));
-    MOCK_METHOD(void, setGpuFenceTime,
-                (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
-    MOCK_METHOD(void, setHwcValidateTiming,
-                (DisplayId displayId, TimePoint validateStartTime, TimePoint validateEndTime),
-                (override));
-    MOCK_METHOD(void, setHwcPresentTiming,
-                (DisplayId displayId, TimePoint presentStartTime, TimePoint presentEndTime),
-                (override));
-    MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override));
-    MOCK_METHOD(void, setRequiresRenderEngine, (DisplayId displayId, bool requiresRenderEngine),
-                (override));
-    MOCK_METHOD(void, setExpectedPresentTime, (TimePoint expectedPresentTime), (override));
-    MOCK_METHOD(void, setSfPresentTiming, (TimePoint presentFenceTime, TimePoint presentEndTime),
-                (override));
-    MOCK_METHOD(void, setHwcPresentDelayedTime,
-                (DisplayId displayId, TimePoint earliestFrameStartTime));
-    MOCK_METHOD(void, setFrameDelay, (Duration frameDelayDuration), (override));
-    MOCK_METHOD(void, setCommitStart, (TimePoint commitStartTime), (override));
-    MOCK_METHOD(void, setCompositeEnd, (TimePoint compositeEndTime), (override));
-    MOCK_METHOD(void, setDisplays, (std::vector<DisplayId> & displayIds), (override));
-    MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (Duration targetDuration), (override));
-};
-
-} // namespace mock
-} // namespace Hwc2
-} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index b21533a..f2c5672 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -23,14 +23,14 @@
 #include <compositionengine/mock/Output.h>
 #include <gtest/gtest.h>
 #include <log/log.h>
-
 #include <renderengine/impl/ExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
+#include <ui/FloatRect.h>
 #include <ui/PixelFormat.h>
-#include "MockHWC2.h"
-#include "MockHWComposer.h"
+
 #include "RegionMatcher.h"
-#include "ui/FloatRect.h"
+#include "mock/DisplayHardware/MockHWC2.h"
+#include "mock/DisplayHardware/MockHWComposer.h"
 
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 1c18cd2..fe7dd9a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -40,8 +40,8 @@
 #include <common/FlagManager.h>
 #include <common/test/FlagUtils.h>
 #include "CallOrderStateMachineHelper.h"
-#include "MockHWC2.h"
 #include "RegionMatcher.h"
+#include "mock/DisplayHardware/MockHWC2.h"
 
 namespace android::compositionengine {
 namespace {
@@ -785,17 +785,20 @@
     InjectedLayer layer3;
 
     uint32_t z = 0;
-    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180));
+    EXPECT_CALL(*layer1.outputLayer,
+                updateCompositionState(false, false, ui::Transform::ROT_180, _));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180));
+    EXPECT_CALL(*layer2.outputLayer,
+                updateCompositionState(false, false, ui::Transform::ROT_180, _));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180));
+    EXPECT_CALL(*layer3.outputLayer,
+                updateCompositionState(false, false, ui::Transform::ROT_180, _));
     EXPECT_CALL(*layer3.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
@@ -822,17 +825,17 @@
     InjectedLayer layer3;
 
     uint32_t z = 0;
-    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer3.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer3.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer3.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
@@ -858,17 +861,17 @@
     InjectedLayer layer3;
 
     uint32_t z = 0;
-    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer3.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
@@ -896,11 +899,11 @@
     InjectedLayer layer3;
 
     InSequence seq;
-    EXPECT_CALL(*layer0.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer0.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0, _));
+    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer3.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0, _));
+    EXPECT_CALL(*layer3.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0, _));
 
     uint32_t z = 0;
     EXPECT_CALL(*layer0.outputLayer,
@@ -4932,12 +4935,12 @@
 
     uint32_t z = 0;
     // Layer requesting blur, or below, should request client composition, unless opaque.
-    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
@@ -4966,17 +4969,17 @@
 
     uint32_t z = 0;
     // Layer requesting blur, or below, should request client composition.
-    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer3.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
@@ -5006,17 +5009,17 @@
 
     uint32_t z = 0;
     // Layer requesting blur, or below, should request client composition.
-    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer1.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
     EXPECT_CALL(*layer1.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer2.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
     EXPECT_CALL(*layer2.outputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0));
+    EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0, _));
     EXPECT_CALL(*layer3.outputLayer,
                 writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, z++,
                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 77bd804..5814aa4 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -44,7 +44,7 @@
 using aidl::android::hardware::graphics::composer3::BnComposerCallback;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness;
-using aidl::android::hardware::graphics::composer3::Lut;
+using aidl::android::hardware::graphics::composer3::Luts;
 using aidl::android::hardware::graphics::composer3::PowerMode;
 using aidl::android::hardware::graphics::composer3::VirtualDisplay;
 
@@ -1565,7 +1565,7 @@
     return error;
 }
 
-Error AidlComposer::setLayerLuts(Display display, Layer layer, std::vector<Lut>& luts) {
+Error AidlComposer::setLayerLuts(Display display, Layer layer, Luts& luts) {
     Error error = Error::NONE;
     mMutex.lock_shared();
     if (auto writer = getWriter(display)) {
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index cdb67e4..d724b21 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -53,6 +53,7 @@
 using aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using aidl::android::hardware::graphics::composer3::ComposerClientReader;
 using aidl::android::hardware::graphics::composer3::ComposerClientWriter;
+using aidl::android::hardware::graphics::composer3::Luts;
 using aidl::android::hardware::graphics::composer3::OverlayProperties;
 
 class AidlIComposerCallbackWrapper;
@@ -248,9 +249,7 @@
             Display display, std::vector<Layer>* outLayers,
             std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*
                     outLuts) override;
-    Error setLayerLuts(
-            Display display, Layer layer,
-            std::vector<aidl::android::hardware::graphics::composer3::Lut>& luts) override;
+    Error setLayerLuts(Display display, Layer layer, Luts& luts) override;
 
 private:
     // Many public functions above simply write a command into the command
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 0905663..42ddcd1 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -42,7 +42,6 @@
 #include <aidl/android/hardware/graphics/composer3/DisplayConfiguration.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayLuts.h>
 #include <aidl/android/hardware/graphics/composer3/IComposerCallback.h>
-#include <aidl/android/hardware/graphics/composer3/Lut.h>
 #include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 
 #include <aidl/android/hardware/graphics/common/Transform.h>
@@ -307,7 +306,7 @@
                                         int32_t frameIntervalNs) = 0;
     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;
+    virtual Error setLayerLuts(Display display, Layer layer, V3_0::Luts& luts) = 0;
 };
 
 } // namespace Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 1df2ab1..5355a12 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -42,7 +42,8 @@
 using AidlCapability = aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
 using aidl::android::hardware::graphics::composer3::DisplayLuts;
-using aidl::android::hardware::graphics::composer3::Lut;
+using aidl::android::hardware::graphics::composer3::LutProperties;
+using aidl::android::hardware::graphics::composer3::Luts;
 using aidl::android::hardware::graphics::composer3::OverlayProperties;
 
 namespace android {
@@ -624,10 +625,18 @@
         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()));
+            if (layerLut.luts.pfd.get() > 0 && layerLut.luts.offsets.has_value()) {
+                const auto& offsets = layerLut.luts.offsets.value();
+                std::vector<std::pair<int32_t, LutProperties>> lutOffsetsAndProperties;
+                lutOffsetsAndProperties.reserve(offsets.size());
+                std::transform(offsets.begin(), offsets.end(), layerLut.luts.lutProperties.begin(),
+                               std::back_inserter(lutOffsetsAndProperties),
+                               [](int32_t i, LutProperties j) { return std::make_pair(i, j); });
+                outLuts->emplace_or_replace(layer.get(), lutOffsetsAndProperties);
+                lutFileDescriptorMapper.emplace_or_replace(layer.get(),
+                                                           ndk::ScopedFileDescriptor(
+                                                                   layerLut.luts.pfd.release()));
+            }
         }
     }
 
@@ -1069,7 +1078,7 @@
     return static_cast<Error>(intError);
 }
 
-Error Layer::setLuts(std::vector<Lut>& luts) {
+Error Layer::setLuts(aidl::android::hardware::graphics::composer3::Luts& luts) {
     if (CC_UNLIKELY(!mDisplay)) {
         return Error::BAD_DISPLAY;
     }
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 61f92f4..799fd02 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -46,7 +46,7 @@
 #include <aidl/android/hardware/graphics/composer3/Color.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
-#include <aidl/android/hardware/graphics/composer3/Lut.h>
+#include <aidl/android/hardware/graphics/composer3/Luts.h>
 #include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 #include <aidl/android/hardware/graphics/composer3/RefreshRateChangedDebugData.h>
 
@@ -109,9 +109,10 @@
     virtual std::optional<ui::Size> getPhysicalSizeInMm() const = 0;
 
     static const int kLutFileDescriptorMapperSize = 20;
+    using LutOffsetAndProperties = std::vector<
+            std::pair<int32_t, aidl::android::hardware::graphics::composer3::LutProperties>>;
     using LayerLuts =
-            ftl::SmallMap<HWC2::Layer*, aidl::android::hardware::graphics::composer3::LutProperties,
-                          kLutFileDescriptorMapperSize>;
+            ftl::SmallMap<HWC2::Layer*, LutOffsetAndProperties, kLutFileDescriptorMapperSize>;
     using LutFileDescriptorMapper =
             ftl::SmallMap<HWC2::Layer*, ndk::ScopedFileDescriptor, kLutFileDescriptorMapperSize>;
 
@@ -375,7 +376,7 @@
     [[nodiscard]] virtual hal::Error setBrightness(float brightness) = 0;
     [[nodiscard]] virtual hal::Error setBlockingRegion(const android::Region& region) = 0;
     [[nodiscard]] virtual hal::Error setLuts(
-            std::vector<aidl::android::hardware::graphics::composer3::Lut>& luts) = 0;
+            aidl::android::hardware::graphics::composer3::Luts& luts) = 0;
 };
 
 namespace impl {
@@ -426,8 +427,7 @@
     // AIDL HAL
     hal::Error setBrightness(float brightness) override;
     hal::Error setBlockingRegion(const android::Region& region) override;
-    hal::Error setLuts(
-            std::vector<aidl::android::hardware::graphics::composer3::Lut>& luts) override;
+    hal::Error setLuts(aidl::android::hardware::graphics::composer3::Luts&) override;
 
 private:
     // These are references to data owned by HWComposer, which will outlive
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index 056ecd7..6a7a09b 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -47,7 +47,7 @@
 using aidl::android::hardware::graphics::composer3::DimmingStage;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
 using aidl::android::hardware::graphics::composer3::DisplayLuts;
-using aidl::android::hardware::graphics::composer3::Lut;
+using aidl::android::hardware::graphics::composer3::Luts;
 using aidl::android::hardware::graphics::composer3::OverlayProperties;
 
 namespace android {
@@ -1415,7 +1415,7 @@
     return Error::NONE;
 }
 
-Error HidlComposer::setLayerLuts(Display, Layer, std::vector<Lut>&) {
+Error HidlComposer::setLayerLuts(Display, Layer, Luts&) {
     return Error::NONE;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 1cc23d1..a3d1f7f 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -356,7 +356,7 @@
             std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*)
             override;
     Error setLayerLuts(Display, Layer,
-                       std::vector<aidl::android::hardware::graphics::composer3::Lut>&) override;
+                       aidl::android::hardware::graphics::composer3::Luts&) override;
 
 private:
     class CommandWriter : public CommandWriterBase {
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index 334c104..c914ec3 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -240,7 +240,8 @@
                                                                  &mSessionConfig);
             if (ret.isOk()) {
                 mHintSession = ret.value();
-                if (FlagManager::getInstance().adpf_use_fmq_channel_fixed()) {
+                if (FlagManager::getInstance().adpf_use_fmq_channel_fixed() &&
+                    FlagManager::getInstance().adpf_fmq_sf()) {
                     setUpFmq();
                 }
             }
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index e5f6b7b..11b674b 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -513,6 +513,10 @@
         isOpaque = contentOpaque && !roundedCorner.hasRoundedCorners() && color.a == 1.f;
         blendMode = getBlendMode(requested);
     }
+
+    if (forceUpdate || requested.what & layer_state_t::eLutsChanged) {
+        luts = requested.luts;
+    }
 }
 
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/readme.md b/services/surfaceflinger/FrontEnd/readme.md
index e5f51a5..6258f7e 100644
--- a/services/surfaceflinger/FrontEnd/readme.md
+++ b/services/surfaceflinger/FrontEnd/readme.md
@@ -17,6 +17,29 @@
 This allows control to be delegated to different parts of the system - such as SystemServer,
 SysUI and Apps.
 
+### Layer Drawing Order
+Layers are drawn based on an inorder traversal, treating relative parents as
+direct parents. Negative z-values place layers below their parent, while
+non-negative values place them above. Layers with the same z-value are drawn
+in creation order (newer on top).  However, relying on creation order for
+z-ordering is discouraged; use unique z-values whenever possible for better
+control.
+
+Traversal pseudo code:
+```
+fn traverseBottomToTop(root):
+  for each child node including relative children,
+    sorted by z then layer id, with z less than 0:
+          traverseBottomToTop(childNode)
+
+  visit(root)
+
+  for each child node including relative children,
+    sorted by z then layer id, with z greater than
+    or equal to 0:
+          traverseBottomToTop(childNode)
+```
+
 ### Layer Lifecycle
 Layer is created by a client. The client receives a strong binder reference to the layer
 handle, which will keep the layer alive as long as the client holds the reference. The
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index c88092b..20ba45f 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -689,8 +689,20 @@
         listener->onReleaseBuffer(callbackId, fence, currentMaxAcquiredBufferCount);
     }
 
-    if (mBufferReleaseChannel) {
-        mBufferReleaseChannel->writeReleaseFence(callbackId, fence, currentMaxAcquiredBufferCount);
+    if (!mBufferReleaseChannel) {
+        return;
+    }
+
+    status_t status = mBufferReleaseChannel->writeReleaseFence(callbackId, fence,
+                                                               currentMaxAcquiredBufferCount);
+    if (status != OK) {
+        int error = -status;
+        // callReleaseBufferCallback is called during Layer's destructor. In this case, it's
+        // expected to receive connection errors.
+        if (error != EPIPE && error != ECONNRESET) {
+            ALOGD("[%s] writeReleaseFence failed. error %d (%s)", getDebugName(), error,
+                  strerror(error));
+        }
     }
 }
 
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index f64ba9e..a346981 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -174,6 +174,7 @@
     layerSettings.edgeExtensionEffect = mSnapshot->edgeExtensionEffect;
     // Record the name of the layer for debugging further down the stack.
     layerSettings.name = mSnapshot->name;
+    layerSettings.luts = mSnapshot->luts;
 
     if (hasEffect() && !hasBufferOrSidebandStream()) {
         prepareEffectsClientComposition(layerSettings, targetSettings);
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 011fd9e..21d3396 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -353,22 +353,13 @@
                               sampledBounds.getSize(), ui::Dataspace::V0_SRGB, displayWeak,
                               RenderArea::Options::CAPTURE_SECURE_LAYERS);
 
-    FenceResult fenceResult;
-    if (FlagManager::getInstance().single_hop_screenshot() &&
-        mFlinger.mRenderEngine->isThreaded()) {
-        std::vector<sp<LayerFE>> layerFEs;
-        auto displayState = mFlinger.getSnapshotsFromMainThread(renderAreaBuilder,
-                                                                getLayerSnapshotsFn, layerFEs);
-        fenceResult = mFlinger.captureScreenshot(renderAreaBuilder, buffer, kRegionSampling,
-                                                 kGrayscale, kIsProtected, kAttachGainmap, nullptr,
-                                                 displayState, layerFEs)
-                              .get();
-    } else {
-        fenceResult = mFlinger.captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn,
-                                                       buffer, kRegionSampling, kGrayscale,
-                                                       kIsProtected, kAttachGainmap, nullptr)
-                              .get();
-    }
+    std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
+    auto displayState =
+            mFlinger.getSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn, layers);
+    FenceResult fenceResult =
+            mFlinger.captureScreenshot(renderAreaBuilder, buffer, kRegionSampling, kGrayscale,
+                                       kIsProtected, kAttachGainmap, nullptr, displayState, layers)
+                    .get();
     if (fenceResult.ok()) {
         fenceResult.value()->waitForever(LOG_TAG);
     }
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 6d05bb1..ad067be 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -722,13 +722,17 @@
             const bool inPrimaryPhysicalRange =
                     policy->primaryRanges.physical.includes(modePtr->getPeakFps());
             const bool inPrimaryRenderRange = policy->primaryRanges.render.includes(fps);
-            if (((policy->primaryRangeIsSingleRate() && !inPrimaryPhysicalRange) ||
+            if (!mIsVrrDevice.load() &&
+                ((policy->primaryRangeIsSingleRate() && !inPrimaryPhysicalRange) ||
                  !inPrimaryRenderRange) &&
                 !(layer.focused &&
                   (layer.vote == LayerVoteType::ExplicitDefault ||
                    layer.vote == LayerVoteType::ExplicitExact))) {
                 // Only focused layers with ExplicitDefault frame rate settings are allowed to score
                 // refresh rates outside the primary range.
+                ALOGV("%s ignores %s (primaryRangeIsSingleRate). Current mode = %s",
+                      formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(),
+                      to_string(activeMode).c_str());
                 continue;
             }
 
@@ -852,7 +856,8 @@
                                    to_string(descending.front().frameRateMode.fps).c_str());
             return {descending, kNoSignals};
         } else {
-            ALOGV("primaryRangeIsSingleRate");
+            ALOGV("%s (primaryRangeIsSingleRate)",
+                  to_string(ranking.front().frameRateMode.fps).c_str());
             SFTRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)",
                                    to_string(ranking.front().frameRateMode.fps).c_str());
             return {ranking, kNoSignals};
@@ -932,6 +937,8 @@
             using LayerVoteType = RefreshRateSelector::LayerVoteType;
 
             if (layer->vote == LayerVoteType::Max || layer->vote == LayerVoteType::Heuristic) {
+                ALOGV("%s: %s skips uid=%d due to the vote", __func__,
+                      formatLayerInfo(*layer, layer->weight).c_str(), layer->ownerUid);
                 skipUid = true;
                 break;
             }
@@ -1014,12 +1021,14 @@
 
         // Layers with ExplicitExactOrMultiple expect touch boost
         if (globalSignals.touch && hasExplicitExactOrMultiple) {
+            ALOGV("%s: Skipping for touch (input signal): uid=%d", __func__, uid);
             continue;
         }
 
         // Mirrors getRankedFrameRates. If there is no ExplicitDefault, expect touch boost and
         // skip frame rate override.
         if (hasHighHint && !hasExplicitDefault) {
+            ALOGV("%s: Skipping for touch (HighHint): uid=%d", __func__, uid);
             continue;
         }
 
@@ -1043,6 +1052,9 @@
                 constexpr bool isSeamlessSwitch = true;
                 const auto layerScore = calculateLayerScoreLocked(*layer, fps, isSeamlessSwitch);
                 score += layer->weight * layerScore;
+                ALOGV("%s: %s gives %s fps score of %.4f", __func__,
+                      formatLayerInfo(*layer, layer->weight).c_str(), to_string(fps).c_str(),
+                      layerScore);
             }
         }
 
@@ -1297,6 +1309,8 @@
     LOG_ALWAYS_FATAL_IF(!activeModeOpt);
     mActiveModeOpt = FrameRateMode{activeModeOpt->get()->getPeakFps(),
                                    ftl::as_non_null(activeModeOpt->get())};
+    mIsVrrDevice = FlagManager::getInstance().vrr_config() &&
+            activeModeOpt->get()->getVrrConfig().has_value();
 
     const auto sortedModes = sortByRefreshRate(mDisplayModes);
     mMinRefreshRateModeIt = sortedModes.front();
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index d3c81b5..8b204a2 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1661,19 +1661,22 @@
         outProperties->combinations.emplace_back(outCombination);
     }
     outProperties->supportMixedColorSpaces = aidlProperties.supportMixedColorSpaces;
-    if (aidlProperties.lutProperties.has_value()) {
+    if (aidlProperties.lutProperties) {
         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));
+        for (auto properties : *aidlProperties.lutProperties) {
+            if (!properties) {
+                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());
     }
@@ -7160,9 +7163,10 @@
 // typically a layer with DRM contents, or have the GRALLOC_USAGE_PROTECTED set on the buffer.
 // A protected layer has no implication on whether it's secure, which is explicitly set by
 // application to avoid being screenshot or drawn via unsecure display.
-bool SurfaceFlinger::layersHasProtectedLayer(const std::vector<sp<LayerFE>>& layers) const {
+bool SurfaceFlinger::layersHasProtectedLayer(
+        const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const {
     bool protectedLayerFound = false;
-    for (auto& layerFE : layers) {
+    for (auto& [_, layerFE] : layers) {
         protectedLayerFound |=
                 (layerFE->mSnapshot->isVisible && layerFE->mSnapshot->hasProtectedContent);
         if (protectedLayerFound) {
@@ -7178,15 +7182,21 @@
 // risk of deadlocks.
 std::optional<SurfaceFlinger::OutputCompositionState> SurfaceFlinger::getSnapshotsFromMainThread(
         RenderAreaBuilderVariant& renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn,
-        std::vector<sp<LayerFE>>& layerFEs) {
+        std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) {
     return mScheduler
-            ->schedule([=, this, &renderAreaBuilder, &layerFEs]() REQUIRES(kMainThreadContext) {
+            ->schedule([=, this, &renderAreaBuilder, &layers]() REQUIRES(kMainThreadContext) {
                 SFTRACE_NAME("getSnapshotsFromMainThread");
-                auto layers = getLayerSnapshotsFn();
-                for (auto& [layer, layerFE] : layers) {
-                    attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK);
+                layers = getLayerSnapshotsFn();
+                // Non-threaded RenderEngine eventually returns to the main thread a 2nd time
+                // to complete the screenshot. Release fences should only be added during the 2nd
+                // hop to main thread in order to avoid potential deadlocks from waiting for the
+                // the future fence to fire.
+                if (mRenderEngine->isThreaded()) {
+                    for (auto& [layer, layerFE] : layers) {
+                        attachReleaseFenceFutureToLayer(layer, layerFE.get(),
+                                                        ui::INVALID_LAYER_STACK);
+                    }
                 }
-                layerFEs = extractLayerFEs(layers);
                 return getDisplayStateFromRenderAreaBuilder(renderAreaBuilder);
             })
             .get();
@@ -7207,79 +7217,41 @@
         return;
     }
 
-    if (FlagManager::getInstance().single_hop_screenshot() && mRenderEngine->isThreaded()) {
-        std::vector<sp<LayerFE>> layerFEs;
-        auto displayState =
-                getSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn, layerFEs);
+    std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
+    auto displayState = getSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn, layers);
 
-        const bool supportsProtected = getRenderEngine().supportsProtectedContent();
-        bool hasProtectedLayer = false;
-        if (allowProtected && supportsProtected) {
-            hasProtectedLayer = layersHasProtectedLayer(layerFEs);
-        }
-        const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
-        const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
-                GRALLOC_USAGE_HW_TEXTURE |
-                (isProtected ? GRALLOC_USAGE_PROTECTED
-                             : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
-        sp<GraphicBuffer> buffer =
-                getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(),
-                                                 static_cast<android_pixel_format>(reqPixelFormat),
-                                                 1 /* layerCount */, usage, "screenshot");
-
-        const status_t bufferStatus = buffer->initCheck();
-        if (bufferStatus != OK) {
-            // Animations may end up being really janky, but don't crash here.
-            // Otherwise an irreponsible process may cause an SF crash by allocating
-            // too much.
-            ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus);
-            invokeScreenCaptureError(bufferStatus, captureListener);
-            return;
-        }
-        const std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
-                renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
-                                                     renderengine::impl::ExternalTexture::Usage::
-                                                             WRITEABLE);
-        auto futureFence = captureScreenshot(renderAreaBuilder, texture, false /* regionSampling */,
-                                             grayscale, isProtected, attachGainmap, captureListener,
-                                             displayState, layerFEs);
-        futureFence.get();
-
-    } else {
-        const bool supportsProtected = getRenderEngine().supportsProtectedContent();
-        bool hasProtectedLayer = false;
-        if (allowProtected && supportsProtected) {
-            auto layers = mScheduler->schedule([=]() { return getLayerSnapshotsFn(); }).get();
-            hasProtectedLayer = layersHasProtectedLayer(extractLayerFEs(layers));
-        }
-        const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
-        const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
-                GRALLOC_USAGE_HW_TEXTURE |
-                (isProtected ? GRALLOC_USAGE_PROTECTED
-                             : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
-        sp<GraphicBuffer> buffer =
-                getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(),
-                                                 static_cast<android_pixel_format>(reqPixelFormat),
-                                                 1 /* layerCount */, usage, "screenshot");
-
-        const status_t bufferStatus = buffer->initCheck();
-        if (bufferStatus != OK) {
-            // Animations may end up being really janky, but don't crash here.
-            // Otherwise an irreponsible process may cause an SF crash by allocating
-            // too much.
-            ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus);
-            invokeScreenCaptureError(bufferStatus, captureListener);
-            return;
-        }
-        const std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
-                renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
-                                                     renderengine::impl::ExternalTexture::Usage::
-                                                             WRITEABLE);
-        auto futureFence = captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn, texture,
-                                                   false /* regionSampling */, grayscale,
-                                                   isProtected, attachGainmap, captureListener);
-        futureFence.get();
+    const bool supportsProtected = getRenderEngine().supportsProtectedContent();
+    bool hasProtectedLayer = false;
+    if (allowProtected && supportsProtected) {
+        hasProtectedLayer = layersHasProtectedLayer(layers);
     }
+    const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
+    const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
+            GRALLOC_USAGE_HW_TEXTURE |
+            (isProtected ? GRALLOC_USAGE_PROTECTED
+                         : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
+    sp<GraphicBuffer> buffer =
+            getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(),
+                                             static_cast<android_pixel_format>(reqPixelFormat),
+                                             1 /* layerCount */, usage, "screenshot");
+
+    const status_t bufferStatus = buffer->initCheck();
+    if (bufferStatus != OK) {
+        // Animations may end up being really janky, but don't crash here.
+        // Otherwise an irreponsible process may cause an SF crash by allocating
+        // too much.
+        ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus);
+        invokeScreenCaptureError(bufferStatus, captureListener);
+        return;
+    }
+    const std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
+            renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
+                                                 renderengine::impl::ExternalTexture::Usage::
+                                                         WRITEABLE);
+    auto futureFence =
+            captureScreenshot(renderAreaBuilder, texture, false /* regionSampling */, grayscale,
+                              isProtected, attachGainmap, captureListener, displayState, layers);
+    futureFence.get();
 }
 
 std::optional<SurfaceFlinger::OutputCompositionState>
@@ -7318,22 +7290,13 @@
     return std::nullopt;
 }
 
-std::vector<sp<LayerFE>> SurfaceFlinger::extractLayerFEs(
-        const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const {
-    std::vector<sp<LayerFE>> layerFEs;
-    layerFEs.reserve(layers.size());
-    for (const auto& [_, layerFE] : layers) {
-        layerFEs.push_back(layerFE);
-    }
-    return layerFEs;
-}
-
 ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshot(
         const RenderAreaBuilderVariant& renderAreaBuilder,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
         bool grayscale, bool isProtected, bool attachGainmap,
         const sp<IScreenCaptureListener>& captureListener,
-        std::optional<OutputCompositionState>& displayState, std::vector<sp<LayerFE>>& layerFEs) {
+        std::optional<OutputCompositionState>& displayState,
+        std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) {
     SFTRACE_CALL();
 
     ScreenCaptureResults captureResults;
@@ -7352,11 +7315,9 @@
     float displayBrightnessNits = displayState.value().displayBrightnessNits;
     float sdrWhitePointNits = displayState.value().sdrWhitePointNits;
 
-    // Empty vector needed to pass into renderScreenImpl for legacy path
-    std::vector<std::pair<Layer*, sp<android::LayerFE>>> layers;
     ftl::SharedFuture<FenceResult> renderFuture =
             renderScreenImpl(renderArea.get(), buffer, regionSampling, grayscale, isProtected,
-                             attachGainmap, captureResults, displayState, layers, layerFEs);
+                             captureResults, displayState, layers);
 
     if (captureResults.capturedHdrLayers && attachGainmap &&
         FlagManager::getInstance().true_hdr_screenshots()) {
@@ -7391,8 +7352,7 @@
             ScreenCaptureResults unusedResults;
             ftl::SharedFuture<FenceResult> hdrRenderFuture =
                     renderScreenImpl(renderArea.get(), hdrTexture, regionSampling, grayscale,
-                                     isProtected, attachGainmap, unusedResults, displayState,
-                                     layers, layerFEs);
+                                     isProtected, unusedResults, displayState, layers);
 
             renderFuture =
                     ftl::Future(std::move(renderFuture))
@@ -7438,75 +7398,14 @@
     return renderFuture;
 }
 
-ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshotLegacy(
-        RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn,
-        const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-        bool grayscale, bool isProtected, bool attachGainmap,
-        const sp<IScreenCaptureListener>& captureListener) {
-    SFTRACE_CALL();
-
-    auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES(
-                                    kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
-        auto layers = getLayerSnapshotsFn();
-        for (auto& [layer, layerFE] : layers) {
-            attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK);
-        }
-        auto displayState = getDisplayStateFromRenderAreaBuilder(renderAreaBuilder);
-
-        ScreenCaptureResults captureResults;
-        std::unique_ptr<const RenderArea> renderArea =
-                std::visit([](auto&& arg) -> std::unique_ptr<RenderArea> { return arg.build(); },
-                           renderAreaBuilder);
-
-        if (!renderArea) {
-            ALOGW("Skipping screen capture because of invalid render area.");
-            if (captureListener) {
-                captureResults.fenceResult = base::unexpected(NO_MEMORY);
-                captureListener->onScreenCaptureCompleted(captureResults);
-            }
-            return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
-        }
-
-        auto layerFEs = extractLayerFEs(layers);
-        ftl::SharedFuture<FenceResult> renderFuture =
-                renderScreenImpl(renderArea.get(), buffer, regionSampling, grayscale, isProtected,
-                                 attachGainmap, captureResults, displayState, layers, layerFEs);
-
-        if (captureListener) {
-            // Defer blocking on renderFuture back to the Binder thread.
-            return ftl::Future(std::move(renderFuture))
-                    .then([captureListener, captureResults = std::move(captureResults)](
-                                  FenceResult fenceResult) mutable -> FenceResult {
-                        captureResults.fenceResult = std::move(fenceResult);
-                        captureListener->onScreenCaptureCompleted(captureResults);
-                        return base::unexpected(NO_ERROR);
-                    })
-                    .share();
-        }
-        return renderFuture;
-    };
-
-    // TODO(b/294936197): Run takeScreenshotsFn() in a binder thread to reduce the number
-    // of calls on the main thread.
-    auto future =
-            mScheduler->schedule(FTL_FAKE_GUARD(kMainThreadContext, std::move(takeScreenshotFn)));
-
-    // Flatten nested futures.
-    auto chain = ftl::Future(std::move(future)).then([](ftl::SharedFuture<FenceResult> future) {
-        return future;
-    });
-
-    return chain.share();
-}
-
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
         const RenderArea* renderArea, const std::shared_ptr<renderengine::ExternalTexture>& buffer,
-        bool regionSampling, bool grayscale, bool isProtected, bool attachGainmap,
-        ScreenCaptureResults& captureResults, std::optional<OutputCompositionState>& displayState,
-        std::vector<std::pair<Layer*, sp<LayerFE>>>& layers, std::vector<sp<LayerFE>>& layerFEs) {
+        bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults& captureResults,
+        std::optional<OutputCompositionState>& displayState,
+        std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) {
     SFTRACE_CALL();
 
-    for (auto& layerFE : layerFEs) {
+    for (auto& [_, layerFE] : layers) {
         frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get();
         captureResults.capturedSecureLayers |= (snapshot->isVisible && snapshot->isSecure);
         captureResults.capturedHdrLayers |= isHdrLayer(*snapshot);
@@ -7565,29 +7464,32 @@
     captureResults.buffer = capturedBuffer->getBuffer();
 
     ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK};
-    if (!layerFEs.empty()) {
-        const sp<LayerFE>& layerFE = layerFEs.back();
+    if (!layers.empty()) {
+        const sp<LayerFE>& layerFE = layers.back().second;
         layerStack = layerFE->getCompositionState()->outputFilter.layerStack;
     }
 
-    auto copyLayerFEs = [&layerFEs]() {
-        std::vector<sp<compositionengine::LayerFE>> ceLayerFEs;
-        ceLayerFEs.reserve(layerFEs.size());
-        for (const auto& layerFE : layerFEs) {
-            ceLayerFEs.push_back(layerFE);
-        }
-        return ceLayerFEs;
-    };
-
     auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace,
                     sdrWhitePointNits, displayBrightnessNits, grayscale, isProtected,
-                    layerFEs = copyLayerFEs(), layerStack, regionSampling,
+                    layers = std::move(layers), layerStack, regionSampling,
                     renderArea = std::move(renderArea), renderIntent,
                     enableLocalTonemapping]() -> FenceResult {
         std::unique_ptr<compositionengine::CompositionEngine> compositionEngine =
                 mFactory.createCompositionEngine();
         compositionEngine->setRenderEngine(mRenderEngine.get());
 
+        std::vector<sp<compositionengine::LayerFE>> layerFEs;
+        layerFEs.reserve(layers.size());
+        for (auto& [layer, layerFE] : layers) {
+            // Release fences were not yet added for non-threaded render engine. To avoid
+            // deadlocks between main thread and binder threads waiting for the future fence
+            // result, fences should be added to layers in the same hop onto the main thread.
+            if (!mRenderEngine->isThreaded()) {
+                attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK);
+            }
+            layerFEs.push_back(layerFE);
+        }
+
         compositionengine::Output::ColorProfile colorProfile{.dataspace = dataspace,
                                                              .renderIntent = renderIntent};
 
@@ -7645,13 +7547,9 @@
     //
     // TODO(b/196334700) Once we use RenderEngineThreaded everywhere we can always defer the call
     // to CompositionEngine::present.
-    ftl::SharedFuture<FenceResult> presentFuture;
-    if (FlagManager::getInstance().single_hop_screenshot() && mRenderEngine->isThreaded()) {
-        presentFuture = ftl::yield(present()).share();
-    } else {
-        presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share()
-                                                    : ftl::yield(present()).share();
-    }
+    ftl::SharedFuture<FenceResult> presentFuture = mRenderEngine->isThreaded()
+            ? ftl::yield(present()).share()
+            : mScheduler->schedule(std::move(present)).share();
 
     return presentFuture;
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 31218ed..ad3106c 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -851,13 +851,14 @@
     void attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE, ui::LayerStack layerStack);
 
     // Checks if a protected layer exists in a list of layers.
-    bool layersHasProtectedLayer(const std::vector<sp<LayerFE>>& layers) const;
+    bool layersHasProtectedLayer(const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const;
 
     using OutputCompositionState = compositionengine::impl::OutputCompositionState;
 
     std::optional<OutputCompositionState> getSnapshotsFromMainThread(
             RenderAreaBuilderVariant& renderAreaBuilder,
-            GetLayerSnapshotsFunction getLayerSnapshotsFn, std::vector<sp<LayerFE>>& layerFEs);
+            GetLayerSnapshotsFunction getLayerSnapshotsFn,
+            std::vector<std::pair<Layer*, sp<LayerFE>>>& layers);
 
     void captureScreenCommon(RenderAreaBuilderVariant, GetLayerSnapshotsFunction,
                              ui::Size bufferSize, ui::PixelFormat, bool allowProtected,
@@ -866,32 +867,19 @@
     std::optional<OutputCompositionState> getDisplayStateFromRenderAreaBuilder(
             RenderAreaBuilderVariant& renderAreaBuilder) REQUIRES(kMainThreadContext);
 
-    // Legacy layer raw pointer is not safe to access outside the main thread.
-    // Creates a new vector consisting only of LayerFEs, which can be safely
-    // accessed outside the main thread.
-    std::vector<sp<LayerFE>> extractLayerFEs(
-            const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const;
-
     ftl::SharedFuture<FenceResult> captureScreenshot(
             const RenderAreaBuilderVariant& renderAreaBuilder,
             const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
             bool grayscale, bool isProtected, bool attachGainmap,
             const sp<IScreenCaptureListener>& captureListener,
             std::optional<OutputCompositionState>& displayState,
-            std::vector<sp<LayerFE>>& layerFEs);
-
-    ftl::SharedFuture<FenceResult> captureScreenshotLegacy(
-            RenderAreaBuilderVariant, GetLayerSnapshotsFunction,
-            const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
-            bool grayscale, bool isProtected, bool attachGainmap,
-            const sp<IScreenCaptureListener>&);
+            std::vector<std::pair<Layer*, sp<LayerFE>>>& layers);
 
     ftl::SharedFuture<FenceResult> renderScreenImpl(
             const RenderArea*, const std::shared_ptr<renderengine::ExternalTexture>&,
-            bool regionSampling, bool grayscale, bool isProtected, bool attachGainmap,
-            ScreenCaptureResults&, std::optional<OutputCompositionState>& displayState,
-            std::vector<std::pair<Layer*, sp<LayerFE>>>& layers,
-            std::vector<sp<LayerFE>>& layerFEs);
+            bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults&,
+            std::optional<OutputCompositionState>& displayState,
+            std::vector<std::pair<Layer*, sp<LayerFE>>>& layers);
 
     void readPersistentProperties();
 
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index de4825b..b22ec66 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -144,7 +144,7 @@
                                                     eventStats, handle->previousReleaseCallbackId);
         if (handle->bufferReleaseChannel &&
             handle->previousReleaseCallbackId != ReleaseCallbackId::INVALID_ID) {
-            mBufferReleases.emplace_back(handle->bufferReleaseChannel,
+            mBufferReleases.emplace_back(handle->name, handle->bufferReleaseChannel,
                                          handle->previousReleaseCallbackId,
                                          handle->previousReleaseFence,
                                          handle->currentMaxAcquiredBufferCount);
@@ -159,8 +159,13 @@
 
 void TransactionCallbackInvoker::sendCallbacks(bool onCommitOnly) {
     for (const auto& bufferRelease : mBufferReleases) {
-        bufferRelease.channel->writeReleaseFence(bufferRelease.callbackId, bufferRelease.fence,
-                                                 bufferRelease.currentMaxAcquiredBufferCount);
+        status_t status = bufferRelease.channel
+                                  ->writeReleaseFence(bufferRelease.callbackId, bufferRelease.fence,
+                                                      bufferRelease.currentMaxAcquiredBufferCount);
+        if (status != OK) {
+            ALOGE("[%s] writeReleaseFence failed. error %d (%s)", bufferRelease.layerName.c_str(),
+                  -status, strerror(-status));
+        }
     }
     mBufferReleases.clear();
 
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index d81d8d0..178ddbb 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -83,6 +83,7 @@
         mCompletedTransactions;
 
     struct BufferRelease {
+        std::string layerName;
         std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> channel;
         ReleaseCallbackId callbackId;
         sp<Fence> fence;
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index a331491..feeafcc 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -116,6 +116,7 @@
     DUMP_SERVER_FLAG(adpf_use_fmq_channel);
 
     /// Trunk stable readonly flags ///
+    DUMP_READ_ONLY_FLAG(adpf_fmq_sf);
     DUMP_READ_ONLY_FLAG(connected_display);
     DUMP_READ_ONLY_FLAG(enable_small_area_detection);
     DUMP_READ_ONLY_FLAG(frame_rate_category_mrr);
@@ -152,9 +153,9 @@
     DUMP_READ_ONLY_FLAG(override_trusted_overlay);
     DUMP_READ_ONLY_FLAG(flush_buffer_slots_to_uncache);
     DUMP_READ_ONLY_FLAG(force_compile_graphite_renderengine);
-    DUMP_READ_ONLY_FLAG(single_hop_screenshot);
     DUMP_READ_ONLY_FLAG(trace_frame_rate_override);
     DUMP_READ_ONLY_FLAG(true_hdr_screenshots);
+    DUMP_READ_ONLY_FLAG(display_config_error_hal);
 
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
@@ -220,6 +221,7 @@
                                 "SkiaTracingFeature__use_skia_tracing")
 
 /// Trunk stable readonly flags ///
+FLAG_MANAGER_READ_ONLY_FLAG(adpf_fmq_sf, "")
 FLAG_MANAGER_READ_ONLY_FLAG(connected_display, "")
 FLAG_MANAGER_READ_ONLY_FLAG(enable_small_area_detection, "")
 FLAG_MANAGER_READ_ONLY_FLAG(frame_rate_category_mrr, "debug.sf.frame_rate_category_mrr")
@@ -256,8 +258,8 @@
 FLAG_MANAGER_READ_ONLY_FLAG(override_trusted_overlay, "");
 FLAG_MANAGER_READ_ONLY_FLAG(flush_buffer_slots_to_uncache, "");
 FLAG_MANAGER_READ_ONLY_FLAG(force_compile_graphite_renderengine, "");
-FLAG_MANAGER_READ_ONLY_FLAG(single_hop_screenshot, "");
 FLAG_MANAGER_READ_ONLY_FLAG(true_hdr_screenshots, "debug.sf.true_hdr_screenshots");
+FLAG_MANAGER_READ_ONLY_FLAG(display_config_error_hal, "");
 
 /// Trunk stable server flags ///
 FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index daaf338..e4305d3 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -54,6 +54,7 @@
     bool adpf_use_fmq_channel_fixed() const;
 
     /// Trunk stable readonly flags ///
+    bool adpf_fmq_sf() const;
     bool connected_display() const;
     bool frame_rate_category_mrr() const;
     bool enable_small_area_detection() const;
@@ -90,9 +91,9 @@
     bool override_trusted_overlay() const;
     bool flush_buffer_slots_to_uncache() const;
     bool force_compile_graphite_renderengine() const;
-    bool single_hop_screenshot() const;
     bool trace_frame_rate_override() const;
     bool true_hdr_screenshots() const;
+    bool display_config_error_hal() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp
index ae502cf..1de6b4a 100644
--- a/services/surfaceflinger/fuzzer/Android.bp
+++ b/services/surfaceflinger/fuzzer/Android.bp
@@ -28,16 +28,18 @@
 cc_defaults {
     name: "surfaceflinger_fuzz_defaults",
     static_libs: [
-        "libc++fs",
         "libsurfaceflinger_common",
     ],
     srcs: [
+        ":libsurfaceflinger_backend_mock_sources",
+        ":libsurfaceflinger_mock_sources",
         ":libsurfaceflinger_sources",
     ],
     defaults: [
         "libsurfaceflinger_defaults",
     ],
     header_libs: [
+        "libsurfaceflinger_backend_mock_headers",
         "libsurfaceflinger_headers",
     ],
     cflags: [
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index e40be51..2cb3989 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -4,6 +4,14 @@
 container: "system"
 
 flag {
+  name: "adpf_fmq_sf"
+  namespace: "game"
+  description: "Guards use of the ADPF FMQ system specifically for SurfaceFlinger"
+  bug: "315894228"
+  is_fixed_read_only: true
+} # adpf_fmq_sf
+
+flag {
   name: "adpf_gpu_sf"
   namespace: "game"
   description: "Guards use of the sending ADPF GPU duration hint and load hints from SurfaceFlinger to Power HAL"
@@ -13,12 +21,20 @@
 flag {
   name: "arr_setframerate_api"
   namespace: "core_graphics"
-  description: "New setFrameRate API for Android 16"
+  description: "New SDK Surface#setFrameRate API and Surface.FrameRateParams for Android 16"
   bug: "356987016"
   is_fixed_read_only: true
 } # arr_setframerate_api
 
 flag {
+  name: "arr_surfacecontrol_setframerate_api"
+  namespace: "core_graphics"
+  description: "New SDK SurfaceControl.Transaction#setFrameRate API for Android 16"
+  bug: "356987016"
+  is_fixed_read_only: true
+} # arr_surfacecontrol_setframerate_api
+
+flag {
   name: "ce_fence_promise"
   namespace: "window_surfaces"
   description: "Moves logic for buffer release fences into LayerFE"
@@ -74,6 +90,14 @@
 } # detached_mirror
 
 flag {
+  name: "display_config_error_hal"
+  namespace: "core_graphics"
+  description: "Report HAL display configuration errors like modeset failure or link training failure"
+  bug: "374184110"
+  is_fixed_read_only: true
+} # display_config_error_hal
+
+flag {
   name: "filter_frames_before_trace_starts"
   namespace: "core_graphics"
   description: "Do not trace FrameTimeline events for frames started before the trace started"
diff --git a/services/surfaceflinger/tests/tracing/Android.bp b/services/surfaceflinger/tests/tracing/Android.bp
index bce1406..6eb7f4a 100644
--- a/services/surfaceflinger/tests/tracing/Android.bp
+++ b/services/surfaceflinger/tests/tracing/Android.bp
@@ -35,9 +35,6 @@
         ":libsurfaceflinger_mock_sources",
         "TransactionTraceTestSuite.cpp",
     ],
-    static_libs: [
-        "libc++fs",
-    ],
     header_libs: [
         "libsurfaceflinger_mocks_headers",
     ],
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 40a6fb8..cb8820a 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -22,15 +22,40 @@
     default_team: "trendy_team_android_core_graphics_stack",
 }
 
+// This is a step towards pulling out the "backend" sources to clean up the
+// dependency graph between CompositionEngine and SurfaceFlinger.
+// MockNativeWindow doesn't strictly belong here, but this works for now so
+// that CompositionEngine tests can use these mocks.
 filegroup {
-    name: "libsurfaceflinger_mock_sources",
+    name: "libsurfaceflinger_backend_mock_sources",
     srcs: [
-        "mock/DisplayHardware/MockPowerHalController.cpp",
         "mock/DisplayHardware/MockComposer.cpp",
         "mock/DisplayHardware/MockHWC2.cpp",
+        "mock/DisplayHardware/MockHWComposer.cpp",
         "mock/DisplayHardware/MockIPower.cpp",
         "mock/DisplayHardware/MockPowerHintSessionWrapper.cpp",
         "mock/DisplayHardware/MockPowerAdvisor.cpp",
+        "mock/DisplayHardware/MockPowerHalController.cpp",
+        "mock/system/window/MockNativeWindow.cpp",
+    ],
+}
+
+cc_library_headers {
+    name: "libsurfaceflinger_backend_mock_headers",
+    export_include_dirs: ["."],
+    static_libs: [
+        "libgmock",
+        "libgtest",
+    ],
+    export_static_lib_headers: [
+        "libgmock",
+        "libgtest",
+    ],
+}
+
+filegroup {
+    name: "libsurfaceflinger_mock_sources",
+    srcs: [
         "mock/MockEventThread.cpp",
         "mock/MockFrameTimeline.cpp",
         "mock/MockFrameTracer.cpp",
@@ -39,7 +64,6 @@
         "mock/MockVsyncController.cpp",
         "mock/MockVSyncDispatch.cpp",
         "mock/MockVSyncTracker.cpp",
-        "mock/system/window/MockNativeWindow.cpp",
     ],
 }
 
@@ -57,9 +81,9 @@
         "surfaceflinger_defaults",
     ],
     test_suites: ["device-tests"],
-    static_libs: ["libc++fs"],
     header_libs: ["surfaceflinger_tests_common_headers"],
     srcs: [
+        ":libsurfaceflinger_backend_mock_sources",
         ":libsurfaceflinger_mock_sources",
         ":libsurfaceflinger_sources",
         "libsurfaceflinger_unittest_main.cpp",
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index c879280..8375bb9 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -85,6 +85,7 @@
     int64_t mSessionId = 123;
     SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel, true);
     SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, false);
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::adpf_fmq_sf, false);
 };
 
 bool PowerAdvisorTest::sessionExists() {
@@ -184,6 +185,7 @@
     SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::adpf_gpu_sf,
                       config.adpfGpuFlagOn);
     SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, config.usesFmq);
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::adpf_fmq_sf, config.usesFmq);
     mPowerAdvisor->onBootFinished();
     bool expectsFmqSuccess = config.usesSharedFmqFlag && !config.fmqFull;
     if (config.usesFmq) {
@@ -789,6 +791,7 @@
 
 TEST_F(PowerAdvisorTest, fmq_sendHint) {
     SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, true);
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::adpf_fmq_sf, true);
     mPowerAdvisor->onBootFinished();
     SetUpFmq(true, false);
     auto startTime = uptimeNanos();
@@ -807,6 +810,7 @@
 
 TEST_F(PowerAdvisorTest, fmq_sendHint_noSharedFlag) {
     SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, true);
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::adpf_fmq_sf, true);
     mPowerAdvisor->onBootFinished();
     SetUpFmq(false, false);
     SessionHint hint;
@@ -821,6 +825,7 @@
 
 TEST_F(PowerAdvisorTest, fmq_sendHint_queueFull) {
     SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, true);
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::adpf_fmq_sf, true);
     mPowerAdvisor->onBootFinished();
     SetUpFmq(true, true);
     ASSERT_EQ(mBackendFmq->availableToRead(), 2uL);
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index adbd868..29e1c21 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -308,6 +308,42 @@
                     << " category=" << ftl::enum_string(testCase.frameRateCategory);
         }
     }
+
+    template <class T>
+    std::vector<LayerRequirement> createLayers(const std::initializer_list<T>& surfaceVotes) {
+        std::vector<LayerRequirement> layers;
+        for (auto surfaceVote : surfaceVotes) {
+            ALOGI("**** %s: Adding layers for %s: (desiredFrameRate=%s, voteType=%s), "
+                  "(frameRateCategory=%s)",
+                  __func__, surfaceVote.name.c_str(),
+                  to_string(surfaceVote.desiredFrameRate).c_str(),
+                  ftl::enum_string(surfaceVote.voteType).c_str(),
+                  ftl::enum_string(surfaceVote.frameRateCategory).c_str());
+
+            if (surfaceVote.desiredFrameRate.isValid()) {
+                std::stringstream ss;
+                ss << surfaceVote.name << " (" << surfaceVote.weight << "): ExplicitDefault ("
+                   << to_string(surfaceVote.desiredFrameRate) << ")";
+                LayerRequirement layer = {.name = ss.str(),
+                                          .vote = surfaceVote.voteType,
+                                          .desiredRefreshRate = surfaceVote.desiredFrameRate,
+                                          .weight = surfaceVote.weight};
+                layers.push_back(layer);
+            }
+
+            if (surfaceVote.frameRateCategory != FrameRateCategory::Default) {
+                std::stringstream ss;
+                ss << surfaceVote.name << " (" << surfaceVote.weight << "): ExplicitCategory ("
+                   << ftl::enum_string(surfaceVote.frameRateCategory) << ")";
+                LayerRequirement layer = {.name = ss.str(),
+                                          .vote = LayerVoteType::ExplicitCategory,
+                                          .frameRateCategory = surfaceVote.frameRateCategory,
+                                          .weight = surfaceVote.weight};
+                layers.push_back(layer);
+            }
+        }
+        return layers;
+    }
 };
 
 RefreshRateSelectorTest::RefreshRateSelectorTest() {
@@ -1776,6 +1812,98 @@
             selector);
 }
 
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_multiSurface_arr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    auto selector = createSelector(kVrrMode_120, kModeId120);
+
+    // Switch the policy to be more like an ARR device (primary range is a single rate).
+    constexpr FpsRange k120_120Hz = {120_Hz, 120_Hz};
+    constexpr FpsRange k0_120Hz = {0_Hz, 120_Hz};
+    constexpr FpsRanges kPrimaryRanges = {/*physical*/ k120_120Hz,
+                                          /*render*/ k120_120Hz};
+    constexpr FpsRanges kAppRequestRanges = {/*physical*/ k120_120Hz,
+                                             /*render*/ k0_120Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {/*defaultMode*/ kModeId120, kPrimaryRanges, kAppRequestRanges}));
+
+    // Surface can translate to multiple layers in SF  scheduler due to category and frame rate
+    // value.
+    struct SurfaceVote {
+        // Params
+        std::string name = "";
+        Fps desiredFrameRate = 0_Hz;
+        LayerVoteType voteType = LayerVoteType::ExplicitDefault;
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+        float weight = 1.f;
+    };
+
+    auto layers = createLayers(
+            std::initializer_list<SurfaceVote>{{.name = "60 fixed source",
+                                                .desiredFrameRate = 60_Hz,
+                                                .voteType = LayerVoteType::ExplicitExactOrMultiple,
+                                                .weight = 0.27f},
+                                               {.name = "1 fixed source + NoPreference",
+                                                .desiredFrameRate = 1_Hz,
+                                                .voteType = LayerVoteType::ExplicitExactOrMultiple,
+                                                .frameRateCategory =
+                                                        FrameRateCategory::NoPreference}});
+    auto actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    EXPECT_EQ(60_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+
+    layers = createLayers(
+            std::initializer_list<SurfaceVote>{{.name = "60 fixed source",
+                                                .desiredFrameRate = 60_Hz,
+                                                .voteType = LayerVoteType::ExplicitExactOrMultiple,
+                                                .weight = 0.27f},
+                                               {.name = "1 fixed source + Normal",
+                                                .desiredFrameRate = 1_Hz,
+                                                .voteType = LayerVoteType::ExplicitExactOrMultiple,
+                                                .frameRateCategory = FrameRateCategory::Normal}});
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    EXPECT_EQ(60_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+
+    layers = createLayers(std::initializer_list<SurfaceVote>{
+            {.name = "30 fixed source + NoPreference",
+             .desiredFrameRate = 30_Hz,
+             .voteType = LayerVoteType::ExplicitExactOrMultiple,
+             .frameRateCategory = FrameRateCategory::NoPreference}});
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    EXPECT_EQ(30_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+
+    layers = createLayers(std::initializer_list<SurfaceVote>{
+            {.name = "1 fixed source + NoPreference",
+             .desiredFrameRate = 1_Hz,
+             .voteType = LayerVoteType::ExplicitExactOrMultiple,
+             .frameRateCategory = FrameRateCategory::NoPreference}});
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Result affected by RefreshRateSelector.kMinSupportedFrameRate.
+    EXPECT_EQ(20_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+
+    layers = createLayers(std::initializer_list<SurfaceVote>{
+            {.name = "24 fixed source + NoPreference",
+             .desiredFrameRate = 24_Hz,
+             .voteType = LayerVoteType::ExplicitExactOrMultiple,
+             .frameRateCategory = FrameRateCategory::NoPreference}});
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    EXPECT_EQ(24_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+
+    layers = createLayers(std::initializer_list<SurfaceVote>{
+            {.name = "23.976 fixed source + NoPreference",
+             .desiredFrameRate = 23.976_Hz,
+             .voteType = LayerVoteType::ExplicitExactOrMultiple,
+             .frameRateCategory = FrameRateCategory::NoPreference}});
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Chooses 120 unless certain threshold is set, see tests test23976Chooses120 and
+    // test23976Chooses60IfThresholdIs120.
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+}
+
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_60_120) {
     auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60);
 
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 4dec5f6..6778af3 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -472,12 +472,10 @@
         ScreenCaptureResults captureResults;
         auto displayState = std::optional{display->getCompositionDisplay()->getState()};
         auto layers = getLayerSnapshotsFn();
-        auto layerFEs = mFlinger->extractLayerFEs(layers);
 
         return mFlinger->renderScreenImpl(renderArea.get(), buffer, regionSampling,
                                           false /* grayscale */, false /* isProtected */,
-                                          false /* attachGainmap */, captureResults, displayState,
-                                          layers, layerFEs);
+                                          captureResults, displayState, layers);
     }
 
     auto getLayerSnapshotsForScreenshotsFn(ui::LayerStack layerStack, uint32_t uid) {
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 615cc94..3e6a768 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -185,7 +185,7 @@
             (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>&));
+                (Display, Layer, aidl::android::hardware::graphics::composer3::Luts&));
 };
 
 } // namespace Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index 53ed2e1..121104d 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -126,6 +126,8 @@
                 (uint32_t, const android::sp<android::GraphicBuffer> &,
                  const android::sp<android::Fence> &),
                 (override));
+    MOCK_METHOD(hal::Error, setBufferSlotsToClear,
+                (const std::vector<uint32_t>& slotsToClear, uint32_t activeBufferSlot), (override));
     MOCK_METHOD(hal::Error, setSurfaceDamage, (const android::Region &), (override));
     MOCK_METHOD(hal::Error, setBlendMode, (hal::BlendMode), (override));
     MOCK_METHOD(hal::Error, setColor, (aidl::android::hardware::graphics::composer3::Color),
@@ -147,8 +149,8 @@
                 (const std::string &, bool, const std::vector<uint8_t> &), (override));
     MOCK_METHOD(hal::Error, setBrightness, (float), (override));
     MOCK_METHOD(hal::Error, setBlockingRegion, (const android::Region &), (override));
-    MOCK_METHOD(hal::Error, setLuts,
-                (std::vector<aidl::android::hardware::graphics::composer3::Lut>&), (override));
+    MOCK_METHOD(hal::Error, setLuts, (aidl::android::hardware::graphics::composer3::Luts&),
+                (override));
 };
 
 } // namespace android::HWC2::mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.cpp
similarity index 81%
rename from services/surfaceflinger/CompositionEngine/tests/MockHWComposer.cpp
rename to services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.cpp
index ae52670..f310633 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.cpp
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.cpp
@@ -16,17 +16,11 @@
 
 #include "MockHWComposer.h"
 
-namespace android {
-
-// This will go away once HWComposer is moved into the "backend" library
-HWComposer::~HWComposer() = default;
-
-namespace mock {
+namespace android::mock {
 
 // The Google Mock documentation recommends explicit non-header instantiations
 // for better compile time performance.
 HWComposer::HWComposer() = default;
 HWComposer::~HWComposer() = default;
 
-} // namespace mock
-} // namespace android
+} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h
new file mode 100644
index 0000000..fa7128c
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWComposer.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "DisplayHardware/HWComposer.h"
+
+namespace android::mock {
+
+class HWComposer : public android::HWComposer {
+public:
+    using HWDisplayId = android::hardware::graphics::composer::hal::HWDisplayId;
+    using PowerMode = android::hardware::graphics::composer::hal::PowerMode;
+
+    HWComposer();
+    ~HWComposer() override;
+
+    MOCK_METHOD(void, setCallback, (HWC2::ComposerCallback&), (override));
+    MOCK_METHOD(bool, getDisplayIdentificationData,
+                (HWDisplayId, uint8_t*, DisplayIdentificationData*), (const, override));
+    MOCK_METHOD(bool, hasCapability, (aidl::android::hardware::graphics::composer3::Capability),
+                (const, override));
+    MOCK_METHOD(bool, hasDisplayCapability,
+                (HalDisplayId, aidl::android::hardware::graphics::composer3::DisplayCapability),
+                (const, override));
+
+    MOCK_METHOD(size_t, getMaxVirtualDisplayCount, (), (const, override));
+    MOCK_METHOD(size_t, getMaxVirtualDisplayDimension, (), (const, override));
+    MOCK_METHOD(bool, allocateVirtualDisplay, (HalVirtualDisplayId, ui::Size, ui::PixelFormat*),
+                (override));
+    MOCK_METHOD(void, allocatePhysicalDisplay,
+                (hal::HWDisplayId, PhysicalDisplayId, std::optional<ui::Size>), (override));
+
+    MOCK_METHOD(std::shared_ptr<HWC2::Layer>, createLayer, (HalDisplayId), (override));
+    MOCK_METHOD(status_t, getDeviceCompositionChanges,
+                (HalDisplayId, bool, std::optional<std::chrono::steady_clock::time_point>, nsecs_t,
+                 Fps, std::optional<android::HWComposer::DeviceRequestedChanges>*));
+    MOCK_METHOD(status_t, setClientTarget,
+                (HalDisplayId, uint32_t, const sp<Fence>&, const sp<GraphicBuffer>&, ui::Dataspace,
+                 float),
+                (override));
+    MOCK_METHOD(status_t, presentAndGetReleaseFences,
+                (HalDisplayId, std::optional<std::chrono::steady_clock::time_point>), (override));
+    MOCK_METHOD(status_t, executeCommands, (HalDisplayId));
+    MOCK_METHOD(status_t, setPowerMode, (PhysicalDisplayId, PowerMode), (override));
+    MOCK_METHOD(status_t, setColorTransform, (HalDisplayId, const mat4&), (override));
+    MOCK_METHOD(void, disconnectDisplay, (HalDisplayId), (override));
+    MOCK_METHOD(sp<Fence>, getPresentFence, (HalDisplayId), (const, override));
+    MOCK_METHOD(nsecs_t, getPresentTimestamp, (PhysicalDisplayId), (const, override));
+    MOCK_METHOD(sp<Fence>, getLayerReleaseFence, (HalDisplayId, HWC2::Layer*), (const, override));
+    MOCK_METHOD(status_t, setOutputBuffer,
+                (HalVirtualDisplayId, const sp<Fence>&, const sp<GraphicBuffer>&), (override));
+    MOCK_METHOD(void, clearReleaseFences, (HalDisplayId), (override));
+    MOCK_METHOD(status_t, getHdrCapabilities, (HalDisplayId, HdrCapabilities*), (override));
+    MOCK_METHOD(int32_t, getSupportedPerFrameMetadata, (HalDisplayId), (const, override));
+    MOCK_METHOD(std::vector<ui::RenderIntent>, getRenderIntents, (HalDisplayId, ui::ColorMode),
+                (const, override));
+    MOCK_METHOD(mat4, getDataspaceSaturationMatrix, (HalDisplayId, ui::Dataspace), (override));
+    MOCK_METHOD(status_t, getDisplayedContentSamplingAttributes,
+                (HalDisplayId, ui::PixelFormat*, ui::Dataspace*, uint8_t*), (override));
+    MOCK_METHOD(status_t, setDisplayContentSamplingEnabled, (HalDisplayId, bool, uint8_t, uint64_t),
+                (override));
+    MOCK_METHOD(status_t, getDisplayedContentSample,
+                (HalDisplayId, uint64_t, uint64_t, DisplayedFrameStats*), (override));
+    MOCK_METHOD(ftl::Future<status_t>, setDisplayBrightness,
+                (PhysicalDisplayId, float, float, const Hwc2::Composer::DisplayBrightnessOptions&),
+                (override));
+    MOCK_METHOD(std::optional<DisplayIdentificationInfo>, onHotplug,
+                (hal::HWDisplayId, hal::Connection), (override));
+    MOCK_METHOD(bool, updatesDeviceProductInfoOnHotplugReconnect, (), (const, override));
+    MOCK_METHOD(std::optional<PhysicalDisplayId>, onVsync, (hal::HWDisplayId, int64_t));
+    MOCK_METHOD(void, setVsyncEnabled, (PhysicalDisplayId, hal::Vsync), (override));
+    MOCK_METHOD(bool, isConnected, (PhysicalDisplayId), (const, override));
+    MOCK_METHOD(std::vector<HWComposer::HWCDisplayMode>, getModes, (PhysicalDisplayId, int32_t),
+                (const, override));
+    MOCK_METHOD((ftl::Expected<hal::HWConfigId, status_t>), getActiveMode, (PhysicalDisplayId),
+                (const, override));
+    MOCK_METHOD(std::vector<ui::ColorMode>, getColorModes, (PhysicalDisplayId), (const, override));
+    MOCK_METHOD(status_t, setActiveColorMode, (PhysicalDisplayId, ui::ColorMode, ui::RenderIntent),
+                (override));
+    MOCK_METHOD(ui::DisplayConnectionType, getDisplayConnectionType, (PhysicalDisplayId),
+                (const, override));
+    MOCK_METHOD(bool, isVsyncPeriodSwitchSupported, (PhysicalDisplayId), (const, override));
+    MOCK_METHOD((ftl::Expected<nsecs_t, status_t>), getDisplayVsyncPeriod, (PhysicalDisplayId),
+                (const, override));
+    MOCK_METHOD(status_t, setActiveModeWithConstraints,
+                (PhysicalDisplayId, hal::HWConfigId, const hal::VsyncPeriodChangeConstraints&,
+                 hal::VsyncPeriodChangeTimeline*),
+                (override));
+    MOCK_METHOD(status_t, setBootDisplayMode, (PhysicalDisplayId, hal::HWConfigId), (override));
+    MOCK_METHOD(status_t, clearBootDisplayMode, (PhysicalDisplayId), (override));
+    MOCK_METHOD(std::optional<hal::HWConfigId>, getPreferredBootDisplayMode, (PhysicalDisplayId),
+                (override));
+
+    MOCK_METHOD(std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>,
+                getHdrConversionCapabilities, (), (const, override));
+    MOCK_METHOD(status_t, setHdrConversionStrategy,
+                (aidl::android::hardware::graphics::common::HdrConversionStrategy,
+                 aidl::android::hardware::graphics::common::Hdr*),
+                (override));
+    MOCK_METHOD(status_t, setAutoLowLatencyMode, (PhysicalDisplayId, bool), (override));
+    MOCK_METHOD(status_t, getSupportedContentTypes,
+                (PhysicalDisplayId, std::vector<hal::ContentType>*), (const, override));
+    MOCK_METHOD(status_t, setContentType, (PhysicalDisplayId, hal::ContentType)), (override);
+    MOCK_METHOD((const std::unordered_map<std::string, bool>&), getSupportedLayerGenericMetadata,
+                (), (const, override));
+    MOCK_METHOD(void, dump, (std::string&), (const, override));
+    MOCK_METHOD(void, dumpOverlayProperties, (std::string&), (const, override));
+    MOCK_METHOD(android::Hwc2::Composer*, getComposer, (), (const, override));
+
+    MOCK_METHOD(hal::HWDisplayId, getPrimaryHwcDisplayId, (), (const, override));
+    MOCK_METHOD(PhysicalDisplayId, getPrimaryDisplayId, (), (const, override));
+    MOCK_METHOD(bool, isHeadless, (), (const, override));
+
+    MOCK_METHOD(std::optional<PhysicalDisplayId>, toPhysicalDisplayId, (hal::HWDisplayId),
+                (const, override));
+    MOCK_METHOD(std::optional<hal::HWDisplayId>, fromPhysicalDisplayId, (PhysicalDisplayId),
+                (const, override));
+    MOCK_METHOD(status_t, getDisplayDecorationSupport,
+                (PhysicalDisplayId,
+                 std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
+                         support),
+                (override));
+    MOCK_METHOD(status_t, setIdleTimerEnabled, (PhysicalDisplayId, std::chrono::milliseconds),
+                (override));
+    MOCK_METHOD(bool, hasDisplayIdleTimerCapability, (PhysicalDisplayId), (const, override));
+    MOCK_METHOD(Hwc2::AidlTransform, getPhysicalDisplayOrientation, (PhysicalDisplayId),
+                (const, override));
+    MOCK_METHOD(bool, getValidateSkipped, (HalDisplayId), (const, override));
+    MOCK_METHOD(const aidl::android::hardware::graphics::composer3::OverlayProperties&,
+                getOverlaySupport, (), (const, override));
+    MOCK_METHOD(status_t, setRefreshRateChangedCallbackDebugEnabled, (PhysicalDisplayId, bool));
+    MOCK_METHOD(status_t, notifyExpectedPresent, (PhysicalDisplayId, TimePoint, Fps));
+    MOCK_METHOD(HWC2::Display::LutFileDescriptorMapper&, getLutFileDescriptorMapper, (),
+                (override));
+};
+
+} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
index ed1405b..4c034d7 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
@@ -32,6 +32,7 @@
 using aidl::android::hardware::power::IPowerHintSession;
 using aidl::android::hardware::power::SessionConfig;
 using aidl::android::hardware::power::SessionTag;
+using aidl::android::hardware::power::SupportInfo;
 
 using aidl::android::hardware::power::Mode;
 using android::binder::Status;
@@ -59,6 +60,7 @@
     MOCK_METHOD(ndk::ScopedAStatus, getSessionChannel,
                 (int32_t tgid, int32_t uid, ChannelConfig* _aidl_return), (override));
     MOCK_METHOD(ndk::ScopedAStatus, closeSessionChannel, (int32_t tgid, int32_t uid), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSupportInfo, (SupportInfo * _aidl_return), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
     MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));