Merge "Rename AidlStatusUtil.h to AidlUtil.h" into main
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 2ce3fb0..df1ef29 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -24,6 +24,7 @@
                libs/nativewindow/
                libs/renderengine/
                libs/ui/
+               libs/vibrator/
                libs/vr/
                opengl/libs/
                services/bufferhub/
diff --git a/cmds/flatland/GLHelper.cpp b/cmds/flatland/GLHelper.cpp
index c163095..77e7328 100644
--- a/cmds/flatland/GLHelper.cpp
+++ b/cmds/flatland/GLHelper.cpp
@@ -18,6 +18,7 @@
 
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/SurfaceComposerClient.h>
 #include <ui/DisplayMode.h>
 
@@ -202,6 +203,14 @@
 
 bool GLHelper::createNamedSurfaceTexture(GLuint name, uint32_t w, uint32_t h,
         sp<GLConsumer>* glConsumer, EGLSurface* surface) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    sp<GLConsumer> glc = new GLConsumer(name, GL_TEXTURE_EXTERNAL_OES, false, true);
+    glc->setDefaultBufferSize(w, h);
+    glc->getSurface()->setMaxDequeuedBufferCount(2);
+    glc->setConsumerUsageBits(GRALLOC_USAGE_HW_COMPOSER);
+
+    sp<ANativeWindow> anw = glc->getSurface();
+#else
     sp<IGraphicBufferProducer> producer;
     sp<IGraphicBufferConsumer> consumer;
     BufferQueue::createBufferQueue(&producer, &consumer);
@@ -212,6 +221,7 @@
     glc->setConsumerUsageBits(GRALLOC_USAGE_HW_COMPOSER);
 
     sp<ANativeWindow> anw = new Surface(producer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     EGLSurface s = eglCreateWindowSurface(mDisplay, mConfig, anw.get(), nullptr);
     if (s == EGL_NO_SURFACE) {
         fprintf(stderr, "eglCreateWindowSurface error: %#x\n", eglGetError());
diff --git a/include/input/Resampler.h b/include/input/Resampler.h
index 5980d5d..2892137 100644
--- a/include/input/Resampler.h
+++ b/include/input/Resampler.h
@@ -18,6 +18,7 @@
 
 #include <chrono>
 #include <optional>
+#include <vector>
 
 #include <input/Input.h>
 #include <input/InputTransport.h>
@@ -71,7 +72,15 @@
 
     struct Sample {
         std::chrono::nanoseconds eventTime;
-        Pointer pointer;
+        std::vector<Pointer> pointers;
+
+        std::vector<PointerCoords> asPointerCoords() const {
+            std::vector<PointerCoords> pointersCoords;
+            for (const Pointer& pointer : pointers) {
+                pointersCoords.push_back(pointer.coords);
+            }
+            return pointersCoords;
+        }
     };
 
     /**
@@ -88,12 +97,21 @@
     RingBuffer<Sample> mLatestSamples{/*capacity=*/2};
 
     /**
-     * Adds up to mLatestSamples.capacity() of motionEvent's latest samples to mLatestSamples. (If
+     * Adds up to mLatestSamples.capacity() of motionEvent's latest samples to mLatestSamples. If
      * motionEvent has fewer samples than mLatestSamples.capacity(), then the available samples are
-     * added to mLatestSamples.)
+     * added to mLatestSamples.
      */
     void updateLatestSamples(const MotionEvent& motionEvent);
 
+    static Sample messageToSample(const InputMessage& message);
+
+    /**
+     * Checks if auxiliary sample has the same pointer properties of target sample. That is,
+     * auxiliary pointer IDs must appear in the same order as target pointer IDs, their toolType
+     * must match and be resampleable.
+     */
+    static bool pointerPropertiesResampleable(const Sample& target, const Sample& auxiliary);
+
     /**
      * Checks if there are necessary conditions to interpolate. For example, interpolation cannot
      * take place if samples are too far apart in time. mLatestSamples must have at least one sample
@@ -125,4 +143,4 @@
 
     inline static void addSampleToMotionEvent(const Sample& sample, MotionEvent& motionEvent);
 };
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/binder/BackendUnifiedServiceManager.h b/libs/binder/BackendUnifiedServiceManager.h
index f5d7e66..8f3839f 100644
--- a/libs/binder/BackendUnifiedServiceManager.h
+++ b/libs/binder/BackendUnifiedServiceManager.h
@@ -57,8 +57,6 @@
         return mTheRealServiceManager->getInterfaceDescriptor();
     }
 
-    IBinder* onAsBinder() override { return IInterface::asBinder(mTheRealServiceManager).get(); }
-
 private:
     sp<os::IServiceManager> mTheRealServiceManager;
     void toBinderService(const os::Service& in, os::Service* _out);
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 333f956..c55dd9d 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -79,10 +79,9 @@
 IServiceManager::~IServiceManager() {}
 
 // From the old libbinder IServiceManager interface to IServiceManager.
-class ServiceManagerShim : public IServiceManager
-{
+class CppBackendShim : public IServiceManager {
 public:
-    explicit ServiceManagerShim (const sp<AidlServiceManager>& impl);
+    explicit CppBackendShim(const sp<BackendUnifiedServiceManager>& impl);
 
     sp<IBinder> getService(const String16& name) const override;
     sp<IBinder> checkService(const String16& name) const override;
@@ -136,11 +135,11 @@
                                           sp<RegistrationWaiter>* waiter);
 
     // Directly get the service in a way that, for lazy services, requests the service to be started
-    // if it is not currently started. This way, calls directly to ServiceManagerShim::getService
+    // if it is not currently started. This way, calls directly to CppBackendShim::getService
     // will still have the 5s delay that is expected by a large amount of Android code.
     //
-    // When implementing ServiceManagerShim, use realGetService instead of
-    // mUnifiedServiceManager->getService so that it can be overridden in ServiceManagerHostShim.
+    // When implementing CppBackendShim, use realGetService instead of
+    // mUnifiedServiceManager->getService so that it can be overridden in CppServiceManagerHostShim.
     virtual Status realGetService(const std::string& name, sp<IBinder>* _aidl_return) {
         Service service;
         Status status = mUnifiedServiceManager->getService2(name, &service);
@@ -155,7 +154,7 @@
 sp<IServiceManager> defaultServiceManager()
 {
     std::call_once(gSmOnce, []() {
-        gDefaultServiceManager = sp<ServiceManagerShim>::make(getBackendUnifiedServiceManager());
+        gDefaultServiceManager = sp<CppBackendShim>::make(getBackendUnifiedServiceManager());
     });
 
     return gDefaultServiceManager;
@@ -279,16 +278,14 @@
 
 // ----------------------------------------------------------------------
 
-ServiceManagerShim::ServiceManagerShim(const sp<AidlServiceManager>& impl) {
-    mUnifiedServiceManager = sp<BackendUnifiedServiceManager>::make(impl);
-}
+CppBackendShim::CppBackendShim(const sp<BackendUnifiedServiceManager>& impl)
+      : mUnifiedServiceManager(impl) {}
 
 // This implementation could be simplified and made more efficient by delegating
 // to waitForService. However, this changes the threading structure in some
 // cases and could potentially break prebuilts. Once we have higher logistical
 // complexity, this could be attempted.
-sp<IBinder> ServiceManagerShim::getService(const String16& name) const
-{
+sp<IBinder> CppBackendShim::getService(const String16& name) const {
     static bool gSystemBootCompleted = false;
 
     sp<IBinder> svc = checkService(name);
@@ -332,8 +329,7 @@
     return nullptr;
 }
 
-sp<IBinder> ServiceManagerShim::checkService(const String16& name) const
-{
+sp<IBinder> CppBackendShim::checkService(const String16& name) const {
     Service ret;
     if (!mUnifiedServiceManager->checkService(String8(name).c_str(), &ret).isOk()) {
         return nullptr;
@@ -341,16 +337,14 @@
     return ret.get<Service::Tag::binder>();
 }
 
-status_t ServiceManagerShim::addService(const String16& name, const sp<IBinder>& service,
-                                        bool allowIsolated, int dumpsysPriority)
-{
+status_t CppBackendShim::addService(const String16& name, const sp<IBinder>& service,
+                                    bool allowIsolated, int dumpsysPriority) {
     Status status = mUnifiedServiceManager->addService(String8(name).c_str(), service,
                                                        allowIsolated, dumpsysPriority);
     return status.exceptionCode();
 }
 
-Vector<String16> ServiceManagerShim::listServices(int dumpsysPriority)
-{
+Vector<String16> CppBackendShim::listServices(int dumpsysPriority) {
     std::vector<std::string> ret;
     if (!mUnifiedServiceManager->listServices(dumpsysPriority, &ret).isOk()) {
         return {};
@@ -364,8 +358,7 @@
     return res;
 }
 
-sp<IBinder> ServiceManagerShim::waitForService(const String16& name16)
-{
+sp<IBinder> CppBackendShim::waitForService(const String16& name16) {
     class Waiter : public android::os::BnServiceCallback {
         Status onRegistration(const std::string& /*name*/,
                               const sp<IBinder>& binder) override {
@@ -454,7 +447,7 @@
     }
 }
 
-bool ServiceManagerShim::isDeclared(const String16& name) {
+bool CppBackendShim::isDeclared(const String16& name) {
     bool declared;
     if (Status status = mUnifiedServiceManager->isDeclared(String8(name).c_str(), &declared);
         !status.isOk()) {
@@ -465,7 +458,7 @@
     return declared;
 }
 
-Vector<String16> ServiceManagerShim::getDeclaredInstances(const String16& interface) {
+Vector<String16> CppBackendShim::getDeclaredInstances(const String16& interface) {
     std::vector<std::string> out;
     if (Status status =
                 mUnifiedServiceManager->getDeclaredInstances(String8(interface).c_str(), &out);
@@ -483,7 +476,7 @@
     return res;
 }
 
-std::optional<String16> ServiceManagerShim::updatableViaApex(const String16& name) {
+std::optional<String16> CppBackendShim::updatableViaApex(const String16& name) {
     std::optional<std::string> declared;
     if (Status status = mUnifiedServiceManager->updatableViaApex(String8(name).c_str(), &declared);
         !status.isOk()) {
@@ -494,7 +487,7 @@
     return declared ? std::optional<String16>(String16(declared.value().c_str())) : std::nullopt;
 }
 
-Vector<String16> ServiceManagerShim::getUpdatableNames(const String16& apexName) {
+Vector<String16> CppBackendShim::getUpdatableNames(const String16& apexName) {
     std::vector<std::string> out;
     if (Status status = mUnifiedServiceManager->getUpdatableNames(String8(apexName).c_str(), &out);
         !status.isOk()) {
@@ -511,7 +504,7 @@
     return res;
 }
 
-std::optional<IServiceManager::ConnectionInfo> ServiceManagerShim::getConnectionInfo(
+std::optional<IServiceManager::ConnectionInfo> CppBackendShim::getConnectionInfo(
         const String16& name) {
     std::optional<os::ConnectionInfo> connectionInfo;
     if (Status status =
@@ -526,8 +519,8 @@
             : std::nullopt;
 }
 
-status_t ServiceManagerShim::registerForNotifications(const String16& name,
-                                                      const sp<AidlRegistrationCallback>& cb) {
+status_t CppBackendShim::registerForNotifications(const String16& name,
+                                                  const sp<AidlRegistrationCallback>& cb) {
     if (cb == nullptr) {
         ALOGE("%s: null cb passed", __FUNCTION__);
         return BAD_VALUE;
@@ -546,9 +539,9 @@
     return OK;
 }
 
-void ServiceManagerShim::removeRegistrationCallbackLocked(const sp<AidlRegistrationCallback>& cb,
-                                                          ServiceCallbackMap::iterator* it,
-                                                          sp<RegistrationWaiter>* waiter) {
+void CppBackendShim::removeRegistrationCallbackLocked(const sp<AidlRegistrationCallback>& cb,
+                                                      ServiceCallbackMap::iterator* it,
+                                                      sp<RegistrationWaiter>* waiter) {
     std::vector<LocalRegistrationAndWaiter>& localRegistrationAndWaiters = (*it)->second;
     for (auto lit = localRegistrationAndWaiters.begin();
          lit != localRegistrationAndWaiters.end();) {
@@ -567,8 +560,8 @@
     }
 }
 
-status_t ServiceManagerShim::unregisterForNotifications(const String16& name,
-                                                        const sp<AidlRegistrationCallback>& cb) {
+status_t CppBackendShim::unregisterForNotifications(const String16& name,
+                                                    const sp<AidlRegistrationCallback>& cb) {
     if (cb == nullptr) {
         ALOGE("%s: null cb passed", __FUNCTION__);
         return BAD_VALUE;
@@ -597,7 +590,7 @@
     return OK;
 }
 
-std::vector<IServiceManager::ServiceDebugInfo> ServiceManagerShim::getServiceDebugInfo() {
+std::vector<IServiceManager::ServiceDebugInfo> CppBackendShim::getServiceDebugInfo() {
     std::vector<os::ServiceDebugInfo> serviceDebugInfos;
     std::vector<IServiceManager::ServiceDebugInfo> ret;
     if (Status status = mUnifiedServiceManager->getServiceDebugInfo(&serviceDebugInfos);
@@ -615,21 +608,21 @@
 }
 
 #ifndef __ANDROID__
-// ServiceManagerShim for host. Implements the old libbinder android::IServiceManager API.
+// CppBackendShim for host. Implements the old libbinder android::IServiceManager API.
 // The internal implementation of the AIDL interface android::os::IServiceManager calls into
 // on-device service manager.
-class ServiceManagerHostShim : public ServiceManagerShim {
+class CppServiceManagerHostShim : public CppBackendShim {
 public:
-    ServiceManagerHostShim(const sp<AidlServiceManager>& impl,
-                           const RpcDelegateServiceManagerOptions& options)
-          : ServiceManagerShim(impl), mOptions(options) {}
-    // ServiceManagerShim::getService is based on checkService, so no need to override it.
+    CppServiceManagerHostShim(const sp<AidlServiceManager>& impl,
+                              const RpcDelegateServiceManagerOptions& options)
+          : CppBackendShim(sp<BackendUnifiedServiceManager>::make(impl)), mOptions(options) {}
+    // CppBackendShim::getService is based on checkService, so no need to override it.
     sp<IBinder> checkService(const String16& name) const override {
         return getDeviceService({String8(name).c_str()}, mOptions);
     }
 
 protected:
-    // Override realGetService for ServiceManagerShim::waitForService.
+    // Override realGetService for CppBackendShim::waitForService.
     Status realGetService(const std::string& name, sp<IBinder>* _aidl_return) override {
         *_aidl_return = getDeviceService({"-g", name}, mOptions);
         return Status::ok();
@@ -650,7 +643,7 @@
         ALOGE("getDeviceService(\"manager\") returns non service manager");
         return nullptr;
     }
-    return sp<ServiceManagerHostShim>::make(interface, options);
+    return sp<CppServiceManagerHostShim>::make(interface, options);
 }
 #endif
 
diff --git a/libs/binder/ndk/include_platform/android/binder_process.h b/libs/binder/ndk/include_platform/android/binder_process.h
index 68528e1..6aff994 100644
--- a/libs/binder/ndk/include_platform/android/binder_process.h
+++ b/libs/binder/ndk/include_platform/android/binder_process.h
@@ -47,8 +47,11 @@
  * be called once before startThreadPool. The number of threads can never decrease.
  *
  * This count refers to the number of threads that will be created lazily by the kernel, in
- * addition to the threads created by ABinderProcess_startThreadPool or
- * ABinderProcess_joinThreadPool.
+ * addition to the single threads created by ABinderProcess_startThreadPool (+1) or
+ * ABinderProcess_joinThreadPool (+1). Note: ABinderProcess_startThreadPool starts a thread
+ * itself, but it also enables up to the number of threads passed to this function to start.
+ * This function does not start any threads itself; it only configures
+ * ABinderProcess_startThreadPool.
  *
  * Do not use this from a library. Apps setup their own threadpools, and otherwise, the main
  * function should be responsible for configuring the threadpool for the entire application.
@@ -63,8 +66,8 @@
 bool ABinderProcess_isThreadPoolStarted(void);
 /**
  * This adds the current thread to the threadpool. This thread will be in addition to the thread
- * started by ABinderProcess_startThreadPool and the lazy kernel-started threads specified by
- * ABinderProcess_setThreadPoolMaxThreadCount.
+ * configured with ABinderProcess_setThreadPoolMaxThreadCount and started with
+ * ABinderProcess_startThreadPool.
  *
  * Do not use this from a library. Apps setup their own threadpools, and otherwise, the main
  * function should be responsible for configuring the threadpool for the entire application.
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index 2cec243..c6fd487 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -301,7 +301,8 @@
 
     auto proc = createRpcTestSocketServerProcess({});
 
-    sp<IBinder> someRealBinder = IInterface::asBinder(defaultServiceManager());
+    sp<IBinder> someRealBinder = defaultServiceManager()->getService(String16("activity"));
+    ASSERT_NE(someRealBinder, nullptr);
     sp<IBinder> outBinder;
     EXPECT_EQ(INVALID_OPERATION,
               proc.rootIface->repeatBinder(someRealBinder, &outBinder).transactionError());
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 52383ac..4c3f4a6 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -83,6 +83,55 @@
 
 static const char* kLlndkLibrariesTxtPath = "/system/etc/llndk.libraries.txt";
 
+// List of libraries that were previously available via VNDK-SP,
+// and are now available via SPHAL.
+// On modern devices that lack the VNDK APEX, the device no longer
+// contains a helpful list of these libraries on the filesystem as above.
+// See system/sepolicy/vendor/file_contexts
+static const char* kFormerlyVndkspLibrariesList =
+    "android.hardware.common-V2-ndk.so:"
+    "android.hardware.common.fmq-V1-ndk.so:"
+    "android.hardware.graphics.allocator-V2-ndk.so:"
+    "android.hardware.graphics.common-V5-ndk.so:"
+    "android.hardware.graphics.common@1.0.so:"
+    "android.hardware.graphics.common@1.1.so:"
+    "android.hardware.graphics.common@1.2.so:"
+    "android.hardware.graphics.composer3-V1-ndk.so:"
+    "android.hardware.graphics.mapper@2.0.so:"
+    "android.hardware.graphics.mapper@2.1.so:"
+    "android.hardware.graphics.mapper@3.0.so:"
+    "android.hardware.graphics.mapper@4.0.so:"
+    "android.hardware.renderscript@1.0.so:"
+    "android.hidl.memory.token@1.0.so:"
+    "android.hidl.memory@1.0-impl.so:"
+    "android.hidl.memory@1.0.so:"
+    "android.hidl.safe_union@1.0.so:"
+    "libRSCpuRef.so:"
+    "libRSDriver.so:"
+    "libRS_internal.so:"
+    "libbacktrace.so:"
+    "libbase.so:"
+    "libbcinfo.so:"
+    "libblas.so:"
+    "libc++.so:"
+    "libcompiler_rt.so:"
+    "libcutils.so:"
+    "libdmabufheap.so:"
+    "libft2.so:"
+    "libgralloctypes.so:"
+    "libhardware.so:"
+    "libhidlbase.so:"
+    "libhidlmemory.so:"
+    "libion.so:"
+    "libjsoncpp.so:"
+    "liblzma.so:"
+    "libpng.so:"
+    "libprocessgroup.so:"
+    "libunwindstack.so:"
+    "libutils.so:"
+    "libutilscallstack.so:"
+    "libz.so";
+
 static std::string vndkVersionStr() {
 #ifdef __BIONIC__
     return base::GetProperty("ro.vndk.version", "");
@@ -122,8 +171,12 @@
 static const std::string getSystemNativeLibraries(NativeLibrary type) {
     std::string nativeLibrariesSystemConfig = "";
 
-    if (!isVndkEnabled() && type == NativeLibrary::LLNDK) {
-        nativeLibrariesSystemConfig = kLlndkLibrariesTxtPath;
+    if (!isVndkEnabled()) {
+        if (type == NativeLibrary::VNDKSP) {
+            return kFormerlyVndkspLibrariesList;
+        } else {
+            nativeLibrariesSystemConfig = kLlndkLibrariesTxtPath;
+        }
     } else {
         nativeLibrariesSystemConfig = kNativeLibrariesSystemConfigPath[type];
         insertVndkVersionStr(&nativeLibrariesSystemConfig);
@@ -263,7 +316,7 @@
         ALOGI("Driver path is setup via UPDATABLE_GFX_DRIVER: %s", mDriverPath.c_str());
     }
 
-    auto vndkNamespace = android_get_exported_namespace("vndk");
+    auto vndkNamespace = android_get_exported_namespace(isVndkEnabled() ? "vndk" : "sphal");
     if (!vndkNamespace) {
         mDriverNamespace = nullptr;
         return mDriverNamespace;
@@ -631,7 +684,7 @@
         return mAngleNamespace;
     }
 
-    auto vndkNamespace = android_get_exported_namespace("vndk");
+    auto vndkNamespace = android_get_exported_namespace(isVndkEnabled() ? "vndk" : "sphal");
     if (!vndkNamespace) {
         mAngleNamespace = nullptr;
         return mAngleNamespace;
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index cda4985..f065ffa 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -510,7 +510,7 @@
         return;
     }
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
-    if (!it->second.mIsStale) {
+    if (!it->second.disconnectedAfterAcquired) {
         mNumAcquired--;
     }
 #else
@@ -566,7 +566,7 @@
         applyTransaction = false;
     }
 
-    BufferItem bufferItem;
+    BLASTBufferItem bufferItem;
 
     status_t status =
             mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false);
@@ -1130,9 +1130,9 @@
 // can be non-blocking when the producer is in the client process.
 class BBQBufferQueueProducer : public BufferQueueProducer {
 public:
-    BBQBufferQueueProducer(const sp<BufferQueueCore>& core, wp<BLASTBufferQueue> bbq)
+    BBQBufferQueueProducer(const sp<BufferQueueCore>& core, const wp<BLASTBufferQueue>& bbq)
           : BufferQueueProducer(core, false /* consumerIsSurfaceFlinger*/),
-            mBLASTBufferQueue(std::move(bbq)) {}
+            mBLASTBufferQueue(bbq) {}
 
     status_t connect(const sp<IProducerListener>& listener, int api, bool producerControlledByApp,
                      QueueBufferOutput* output) override {
@@ -1156,10 +1156,19 @@
             return status;
         }
 
+        // We need to reset dequeued and acquired counts because BufferQueueProducer::disconnect
+        // calls BufferQueueCore::freeAllBuffersLocked which frees all dequeued and acquired
+        // buffers. We don't reset mNumFrameAvailable because these buffers are still available
+        // in BufferItemConsumer.
         bbq->mNumDequeued = 0;
         bbq->mNumAcquired = 0;
+        // SurfaceFlinger sends release callbacks for buffers that have been acquired after a
+        // disconnect. We set disconnectedAfterAcquired to true so that we can ignore any stale
+        // releases that come in after the producer is disconnected. Otherwise, releaseBuffer will
+        // decrement mNumAcquired for a buffer that was acquired before we reset mNumAcquired to
+        // zero.
         for (auto& [releaseId, bufferItem] : bbq->mSubmitted) {
-            bufferItem.mIsStale = true;
+            bufferItem.disconnectedAfterAcquired = true;
         }
 
         return OK;
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 4dafc57..c2dcd25 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -198,9 +198,16 @@
     // latch stale buffers and that we don't wait on barriers from an old producer.
     uint32_t mProducerId = 0;
 
+    class BLASTBufferItem : public BufferItem {
+    public:
+        // True if BBQBufferQueueProducer is disconnected after the buffer is acquried but
+        // before it is released.
+        bool disconnectedAfterAcquired{false};
+    };
+
     // Keep a reference to the submitted buffers so we can release when surfaceflinger drops the
     // buffer or the buffer has been presented and a new buffer is ready to be presented.
-    std::unordered_map<ReleaseCallbackId, BufferItem, ReleaseBufferCallbackIdHash> mSubmitted
+    std::unordered_map<ReleaseCallbackId, BLASTBufferItem, ReleaseBufferCallbackIdHash> mSubmitted
             GUARDED_BY(mMutex);
 
     // Keep a queue of the released buffers instead of immediately releasing
diff --git a/libs/input/Resampler.cpp b/libs/input/Resampler.cpp
index 342f7f5..c663649 100644
--- a/libs/input/Resampler.cpp
+++ b/libs/input/Resampler.cpp
@@ -21,6 +21,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <ftl/enum.h>
 
 #include <input/Resampler.h>
 #include <utils/Timers.h>
@@ -56,6 +57,11 @@
 
 constexpr std::chrono::milliseconds RESAMPLE_MAX_PREDICTION{8};
 
+bool canResampleTool(ToolType toolType) {
+    return toolType == ToolType::FINGER || toolType == ToolType::MOUSE ||
+            toolType == ToolType::STYLUS || toolType == ToolType::UNKNOWN;
+}
+
 inline float lerp(float a, float b, float alpha) {
     return a + alpha * (b - a);
 }
@@ -73,21 +79,70 @@
 
 void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) {
     const size_t numSamples = motionEvent.getHistorySize() + 1;
-    for (size_t i = 0; i < numSamples; ++i) {
+    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) {
+            // getSamplePointerCoords is the vector representation of a getHistorySize by
+            // getPointerCount matrix.
+            const PointerCoords& pointerCoords =
+                    motionEvent.getSamplePointerCoords()[sampleIndex * numPointers + pointerIndex];
+            pointers.push_back(
+                    Pointer{*motionEvent.getPointerProperties(pointerIndex), pointerCoords});
+        }
         mLatestSamples.pushBack(
-                Sample{static_cast<nanoseconds>(motionEvent.getHistoricalEventTime(i)),
-                       Pointer{*motionEvent.getPointerProperties(0),
-                               motionEvent.getSamplePointerCoords()[i]}});
+                Sample{nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)}, pointers});
     }
 }
 
-bool LegacyResampler::canInterpolate(const InputMessage& futureSample) const {
+LegacyResampler::Sample LegacyResampler::messageToSample(const InputMessage& message) {
+    std::vector<Pointer> pointers;
+    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});
+    }
+    return Sample{nanoseconds{message.body.motion.eventTime}, pointers};
+}
+
+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.";
+            return false;
+        }
+        if (target.pointers[i].properties.toolType != auxiliary.pointers[i].properties.toolType) {
+            LOG_IF(INFO, debugResampling()) << "Not resampled. Pointer ToolType mismatch.";
+            return false;
+        }
+        if (!canResampleTool(target.pointers[i].properties.toolType)) {
+            LOG_IF(INFO, debugResampling())
+                    << "Not resampled. Cannot resample "
+                    << ftl::enum_string(target.pointers[i].properties.toolType) << " ToolType.";
+            return false;
+        }
+    }
+    return true;
+}
+
+bool LegacyResampler::canInterpolate(const InputMessage& message) const {
     LOG_IF(FATAL, mLatestSamples.empty())
             << "Not resampled. mLatestSamples must not be empty to interpolate.";
 
     const Sample& pastSample = *(mLatestSamples.end() - 1);
-    const nanoseconds delta =
-            static_cast<nanoseconds>(futureSample.body.motion.eventTime) - pastSample.eventTime;
+    const Sample& futureSample = messageToSample(message);
+
+    if (!pointerPropertiesResampleable(pastSample, futureSample)) {
+        return false;
+    }
+
+    const nanoseconds delta = futureSample.eventTime - pastSample.eventTime;
     if (delta < RESAMPLE_MIN_DELTA) {
         LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
         return false;
@@ -104,15 +159,20 @@
             << "Not resampled. mLatestSamples must not be empty to interpolate.";
 
     const Sample& pastSample = *(mLatestSamples.end() - 1);
+
     const nanoseconds delta =
-            static_cast<nanoseconds>(futureSample.body.motion.eventTime) - pastSample.eventTime;
+            nanoseconds{futureSample.body.motion.eventTime} - pastSample.eventTime;
     const float alpha =
             std::chrono::duration<float, std::milli>(resampleTime - pastSample.eventTime) / delta;
-    const PointerCoords resampledCoords =
-            calculateResampledCoords(pastSample.pointer.coords,
-                                     futureSample.body.motion.pointers[0].coords, alpha);
 
-    return Sample{resampleTime, Pointer{pastSample.pointer.properties, resampledCoords}};
+    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});
+    }
+    return Sample{resampleTime, resampledPointers};
 }
 
 bool LegacyResampler::canExtrapolate() const {
@@ -124,6 +184,10 @@
     const Sample& pastSample = *(mLatestSamples.end() - 2);
     const Sample& presentSample = *(mLatestSamples.end() - 1);
 
+    if (!pointerPropertiesResampleable(presentSample, pastSample)) {
+        return false;
+    }
+
     const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
     if (delta < RESAMPLE_MIN_DELTA) {
         LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
@@ -160,16 +224,21 @@
     const float alpha =
             std::chrono::duration<float, std::milli>(newResampleTime - pastSample.eventTime) /
             delta;
-    const PointerCoords resampledCoords =
-            calculateResampledCoords(pastSample.pointer.coords, presentSample.pointer.coords,
-                                     alpha);
 
-    return Sample{newResampleTime, Pointer{presentSample.pointer.properties, resampledCoords}};
+    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});
+    }
+    return Sample{newResampleTime, resampledPointers};
 }
 
 inline void LegacyResampler::addSampleToMotionEvent(const Sample& sample,
                                                     MotionEvent& motionEvent) {
-    motionEvent.addSample(sample.eventTime.count(), &sample.pointer.coords, motionEvent.getId());
+    motionEvent.addSample(sample.eventTime.count(), sample.asPointerCoords().data(),
+                          motionEvent.getId());
 }
 
 void LegacyResampler::resampleMotionEvent(nanoseconds resampleTime, MotionEvent& motionEvent,
diff --git a/libs/input/tests/Resampler_test.cpp b/libs/input/tests/Resampler_test.cpp
index 135f8b4..b372c0b 100644
--- a/libs/input/tests/Resampler_test.cpp
+++ b/libs/input/tests/Resampler_test.cpp
@@ -59,6 +59,9 @@
 struct InputSample {
     std::chrono::milliseconds eventTime{0};
     std::vector<Pointer> pointers{};
+
+    explicit InputSample(std::chrono::milliseconds eventTime, const std::vector<Pointer>& pointers)
+          : eventTime{eventTime}, pointers{pointers} {}
     /**
      * Converts from InputSample to InputMessage. Enables calling LegacyResampler methods only with
      * the relevant data for tests.
@@ -73,6 +76,7 @@
     message.body.motion.eventTime = static_cast<std::chrono::nanoseconds>(eventTime).count();
     message.body.motion.source = AINPUT_SOURCE_CLASS_POINTER;
     message.body.motion.downTime = 0;
+
     const uint32_t pointerCount = message.body.motion.pointerCount;
     for (uint32_t i = 0; i < pointerCount; ++i) {
         message.body.motion.pointers[i].properties.id = pointers[i].id;
@@ -132,14 +136,6 @@
 
     std::unique_ptr<Resampler> mResampler;
 
-    MotionEvent buildMotionEvent(const int32_t action, const nsecs_t eventTime,
-                                 const std::vector<PointerBuilder>& pointers);
-
-    InputMessage createMessage(const uint32_t pointerCount, const nsecs_t eventTime,
-                               const int32_t action,
-                               const std::vector<PointerProperties>& properties,
-                               const std::vector<PointerCoords>& coords);
-
     /**
      * Checks that beforeCall and afterCall are equal except for the mutated attributes by addSample
      * member function.
@@ -153,42 +149,14 @@
      * Asserts the MotionEvent is resampled by checking an increment in history size and that the
      * resampled coordinates are near the expected ones.
      */
-    void assertMotionEventIsResampledAndCoordsNear(const MotionEvent& original,
-                                                   const MotionEvent& resampled,
-                                                   const PointerCoords& expectedCoords);
+    void assertMotionEventIsResampledAndCoordsNear(
+            const MotionEvent& original, const MotionEvent& resampled,
+            const std::vector<PointerCoords>& expectedCoords);
 
     void assertMotionEventIsNotResampled(const MotionEvent& original,
                                          const MotionEvent& notResampled);
 };
 
-MotionEvent ResamplerTest::buildMotionEvent(const int32_t action, const nsecs_t eventTime,
-                                            const std::vector<PointerBuilder>& pointerBuilders) {
-    MotionEventBuilder motionEventBuilder = MotionEventBuilder(action, AINPUT_SOURCE_CLASS_POINTER)
-                                                    .downTime(0)
-                                                    .eventTime(eventTime);
-    for (const PointerBuilder& pointerBuilder : pointerBuilders) {
-        motionEventBuilder.pointer(pointerBuilder);
-    }
-    return motionEventBuilder.build();
-}
-
-InputMessage ResamplerTest::createMessage(const uint32_t pointerCount, const nsecs_t eventTime,
-                                          const int32_t action,
-                                          const std::vector<PointerProperties>& properties,
-                                          const std::vector<PointerCoords>& coords) {
-    InputMessage message;
-    message.header.type = InputMessage::Type::MOTION;
-    message.body.motion.pointerCount = pointerCount;
-    message.body.motion.eventTime = eventTime;
-    message.body.motion.source = AINPUT_SOURCE_CLASS_POINTER;
-    message.body.motion.downTime = 0;
-    for (uint32_t i = 0; i < pointerCount; ++i) {
-        message.body.motion.pointers[i].properties = properties[i];
-        message.body.motion.pointers[i].coords = coords[i];
-    }
-    return message;
-}
-
 void ResamplerTest::assertMotionEventMetaDataDidNotMutate(const MotionEvent& beforeCall,
                                                           const MotionEvent& afterCall) {
     EXPECT_EQ(beforeCall.getDeviceId(), afterCall.getDeviceId());
@@ -207,18 +175,29 @@
     EXPECT_EQ(beforeCall.getDisplayId(), afterCall.getDisplayId());
 }
 
-void ResamplerTest::assertMotionEventIsResampledAndCoordsNear(const MotionEvent& original,
-                                                              const MotionEvent& resampled,
-                                                              const PointerCoords& expectedCoords) {
+void ResamplerTest::assertMotionEventIsResampledAndCoordsNear(
+        const MotionEvent& original, const MotionEvent& resampled,
+        const std::vector<PointerCoords>& expectedCoords) {
     assertMotionEventMetaDataDidNotMutate(original, resampled);
+
     const size_t originalSampleSize = original.getHistorySize() + 1;
     const size_t resampledSampleSize = resampled.getHistorySize() + 1;
     EXPECT_EQ(originalSampleSize + 1, resampledSampleSize);
-    const PointerCoords& resampledCoords =
-            resampled.getSamplePointerCoords()[resampled.getHistorySize()];
-    EXPECT_TRUE(resampledCoords.isResampled);
-    EXPECT_NEAR(expectedCoords.getX(), resampledCoords.getX(), EPSILON);
-    EXPECT_NEAR(expectedCoords.getY(), resampledCoords.getY(), EPSILON);
+
+    const size_t numPointers = resampled.getPointerCount();
+    const size_t beginLatestSample = resampledSampleSize - 1;
+    for (size_t i = 0; i < numPointers; ++i) {
+        SCOPED_TRACE(i);
+        EXPECT_EQ(original.getPointerId(i), resampled.getPointerId(i));
+        EXPECT_EQ(original.getToolType(i), resampled.getToolType(i));
+
+        const PointerCoords& resampledCoords =
+                resampled.getSamplePointerCoords()[beginLatestSample * numPointers + i];
+
+        EXPECT_TRUE(resampledCoords.isResampled);
+        EXPECT_NEAR(expectedCoords[i].getX(), resampledCoords.getX(), EPSILON);
+        EXPECT_NEAR(expectedCoords[i].getY(), resampledCoords.getY(), EPSILON);
+    }
 }
 
 void ResamplerTest::assertMotionEventIsNotResampled(const MotionEvent& original,
@@ -233,7 +212,7 @@
     constexpr float TOUCH_MAJOR_VALUE = 1.0f;
 
     MotionEvent motionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
     constexpr std::chrono::nanoseconds eventTime{10ms};
@@ -255,34 +234,40 @@
     EXPECT_EQ(motionEvent.getTouchMajor(0), TOUCH_MAJOR_VALUE);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
-                                              Pointer{.id = 0,
-                                                      .x = 2.2f,
-                                                      .y = 2.4f,
-                                                      .isResampled = true});
+                                              {Pointer{.id = 0,
+                                                       .x = 2.2f,
+                                                       .y = 2.4f,
+                                                       .isResampled = true}});
 }
 
 TEST_F(ResamplerTest, SinglePointerNotEnoughDataToResample) {
     MotionEvent motionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
-                        AMOTION_EVENT_ACTION_MOVE,
-                        .deviceId = 0};
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
     const MotionEvent originalMotionEvent = motionEvent;
-    mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
 
 TEST_F(ResamplerTest, SinglePointerDifferentDeviceIdBetweenMotionEvents) {
     MotionEvent motionFromFirstDevice =
-            InputStream{{{4ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         {8ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+            InputStream{{InputSample{4ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
+                         InputSample{8ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE,
                         .deviceId = 0};
+
     mResampler->resampleMotionEvent(10ms, motionFromFirstDevice, nullptr);
+
     MotionEvent motionFromSecondDevice =
-            InputStream{{{11ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+            InputStream{{InputSample{11ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE,
                         .deviceId = 1};
     const MotionEvent originalMotionEvent = motionFromSecondDevice;
+
     mResampler->resampleMotionEvent(12ms, motionFromSecondDevice, nullptr);
     // The MotionEvent should not be resampled because the second event came from a different device
     // than the previous event.
@@ -308,28 +293,30 @@
  */
 TEST_F(ResamplerTest, SinglePointerSingleSampleInterpolation) {
     MotionEvent motionEvent =
-            InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
     const InputMessage futureSample =
-            InputSample{15ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}};
+            InputSample{15ms, {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}};
 
     const MotionEvent originalMotionEvent = motionEvent;
 
     mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
-                                              Pointer{.id = 0,
-                                                      .x = 1.2f,
-                                                      .y = 1.2f,
-                                                      .isResampled = true});
+                                              {Pointer{.id = 0,
+                                                       .x = 1.2f,
+                                                       .y = 2.4f,
+                                                       .isResampled = true}});
 }
 
 TEST_F(ResamplerTest, SinglePointerDeltaTooSmallInterpolation) {
     MotionEvent motionEvent =
-            InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
     const InputMessage futureSample =
-            InputSample{11ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}};
+            InputSample{11ms, {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}};
 
     const MotionEvent originalMotionEvent = motionEvent;
 
@@ -342,25 +329,26 @@
  * Tests extrapolation given two MotionEvents with a single sample.
  */
 TEST_F(ResamplerTest, SinglePointerSingleSampleExtrapolation) {
-    MotionEvent previousMotionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
-    mResampler->resampleMotionEvent(10ms, previousMotionEvent, nullptr);
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, nullptr);
 
-    MotionEvent motionEvent =
-            InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
-    const MotionEvent originalMotionEvent = motionEvent;
+    const MotionEvent originalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, nullptr);
 
-    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
-                                              Pointer{.id = 0,
-                                                      .x = 1.0f,
-                                                      .y = 1.0f,
-                                                      .isResampled = true});
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, secondMotionEvent,
+                                              {Pointer{.id = 0,
+                                                       .x = 2.2f,
+                                                       .y = 4.4f,
+                                                       .isResampled = true}});
     // Integrity of the whole motionEvent
     // History size should increment by 1
     // Check if the resampled value is the last one
@@ -370,27 +358,30 @@
 
 TEST_F(ResamplerTest, SinglePointerMultipleSampleInterpolation) {
     MotionEvent motionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 2.0f, .y = 3.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
+
     const InputMessage futureSample =
-            InputSample{15ms, {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}};
+            InputSample{15ms, {{.id = 0, .x = 3.0f, .y = 5.0f, .isResampled = false}}};
 
     const MotionEvent originalMotionEvent = motionEvent;
 
     mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
-                                              Pointer{.id = 0,
-                                                      .x = 2.2f,
-                                                      .y = 2.2f,
-                                                      .isResampled = true});
+                                              {Pointer{.id = 0,
+                                                       .x = 2.2f,
+                                                       .y = 3.4f,
+                                                       .isResampled = true}});
 }
 
 TEST_F(ResamplerTest, SinglePointerMultipleSampleExtrapolation) {
     MotionEvent motionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
     const MotionEvent originalMotionEvent = motionEvent;
@@ -398,16 +389,17 @@
     mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
-                                              Pointer{.id = 0,
-                                                      .x = 2.2f,
-                                                      .y = 2.2f,
-                                                      .isResampled = true});
+                                              {Pointer{.id = 0,
+                                                       .x = 2.2f,
+                                                       .y = 4.4f,
+                                                       .isResampled = true}});
 }
 
 TEST_F(ResamplerTest, SinglePointerDeltaTooSmallExtrapolation) {
     MotionEvent motionEvent =
-            InputStream{{{9ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+            InputStream{{InputSample{9ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
     const MotionEvent originalMotionEvent = motionEvent;
@@ -419,8 +411,9 @@
 
 TEST_F(ResamplerTest, SinglePointerDeltaTooLargeExtrapolation) {
     MotionEvent motionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         {26ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{26ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
     const MotionEvent originalMotionEvent = motionEvent;
@@ -432,8 +425,9 @@
 
 TEST_F(ResamplerTest, SinglePointerResampleTimeTooFarExtrapolation) {
     MotionEvent motionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         {25ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{25ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
     const MotionEvent originalMotionEvent = motionEvent;
@@ -441,9 +435,424 @@
     mResampler->resampleMotionEvent(43ms, motionEvent, nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
-                                              Pointer{.id = 0,
-                                                      .x = 2.4f,
-                                                      .y = 2.4f,
-                                                      .isResampled = true});
+                                              {Pointer{.id = 0,
+                                                       .x = 2.4f,
+                                                       .y = 4.8f,
+                                                       .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerSingleSampleInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                         {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.x = 2.2f, .y = 2.2f, .isResampled = true},
+                                               Pointer{.x = 3.2f, .y = 3.2f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerSingleSampleExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, secondMotionEvent,
+                                              {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
+                                               Pointer{.x = 4.4f, .y = 4.4f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerMultipleSampleInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 0, .x = 5.0f, .y = 5.0f, .isResampled = false},
+                         {.id = 1, .x = 6.0f, .y = 6.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
+                                               Pointer{.x = 4.4f, .y = 4.4f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerMultipleSampleExtrapolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
+                                               Pointer{.x = 4.4f, .y = 4.4f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerIncreaseNumPointersInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                         {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                         {.id = 2, .x = 5.0f, .y = 5.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.x = 1.4f, .y = 1.4f, .isResampled = true},
+                                               Pointer{.x = 2.4f, .y = 2.4f, .isResampled = true}});
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{25ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 2, .x = 5.0f, .y = 5.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage secondFutureSample =
+            InputSample{30ms,
+                        {{.id = 0, .x = 5.0f, .y = 5.0f, .isResampled = false},
+                         {.id = 1, .x = 6.0f, .y = 6.0f, .isResampled = false},
+                         {.id = 2, .x = 7.0f, .y = 7.0f, .isResampled = false}}};
+
+    const MotionEvent originalSecondMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(27ms, secondMotionEvent, &secondFutureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalSecondMotionEvent, secondMotionEvent,
+                                              {Pointer{.x = 3.8f, .y = 3.8f, .isResampled = true},
+                                               Pointer{.x = 4.8f, .y = 4.8f, .isResampled = true},
+                                               Pointer{.x = 5.8f, .y = 5.8f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerIncreaseNumPointersExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 2, .x = 5.0f, .y = 5.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDecreaseNumPointersInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 2, .x = 5.0f, .y = 5.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 0, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                         {.id = 1, .x = 5.0f, .y = 5.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDecreaseNumPointersExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false},
+                                      {.id = 2, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(secondOriginalMotionEvent, secondMotionEvent,
+                                              {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
+                                               Pointer{.x = 4.4f, .y = 4.4f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                         {.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentIdsInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                         {.id = 2, .x = 3.0f, .y = 3.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentIdsExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 2, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentToolTypeInterpolation) {
+    MotionEvent motionEvent = InputStream{{InputSample{10ms,
+                                                       {{.id = 0,
+                                                         .toolType = ToolType::FINGER,
+                                                         .x = 1.0f,
+                                                         .y = 1.0f,
+                                                         .isResampled = false},
+                                                        {.id = 1,
+                                                         .toolType = ToolType::FINGER,
+                                                         .x = 2.0f,
+                                                         .y = 2.0f,
+                                                         .isResampled = false}}}},
+                                          AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample = InputSample{15ms,
+                                                  {{.id = 0,
+                                                    .toolType = ToolType::FINGER,
+                                                    .x = 3.0,
+                                                    .y = 3.0,
+                                                    .isResampled = false},
+                                                   {.id = 1,
+                                                    .toolType = ToolType::STYLUS,
+                                                    .x = 4.0,
+                                                    .y = 4.0,
+                                                    .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentToolTypeExtrapolation) {
+    MotionEvent firstMotionEvent = InputStream{{InputSample{5ms,
+                                                            {{.id = 0,
+                                                              .toolType = ToolType::FINGER,
+                                                              .x = 1.0f,
+                                                              .y = 1.0f,
+                                                              .isResampled = false},
+                                                             {.id = 1,
+                                                              .toolType = ToolType::FINGER,
+                                                              .x = 2.0f,
+                                                              .y = 2.0f,
+                                                              .isResampled = false}}}},
+                                               AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent = InputStream{{InputSample{10ms,
+                                                             {{.id = 0,
+                                                               .toolType = ToolType::FINGER,
+                                                               .x = 1.0f,
+                                                               .y = 1.0f,
+                                                               .isResampled = false},
+                                                              {.id = 1,
+                                                               .toolType = ToolType::STYLUS,
+                                                               .x = 2.0f,
+                                                               .y = 2.0f,
+                                                               .isResampled = false}}}},
+                                                AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerShouldNotResampleToolTypeInterpolation) {
+    MotionEvent motionEvent = InputStream{{InputSample{10ms,
+                                                       {{.id = 0,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 1.0f,
+                                                         .y = 1.0f,
+                                                         .isResampled = false},
+                                                        {.id = 1,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 2.0f,
+                                                         .y = 2.0f,
+                                                         .isResampled = false}}}},
+                                          AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample = InputSample{15ms,
+                                                  {{.id = 0,
+                                                    .toolType = ToolType::PALM,
+                                                    .x = 3.0,
+                                                    .y = 3.0,
+                                                    .isResampled = false},
+                                                   {.id = 1,
+                                                    .toolType = ToolType::PALM,
+                                                    .x = 4.0,
+                                                    .y = 4.0,
+                                                    .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerShouldNotResampleToolTypeExtrapolation) {
+    MotionEvent motionEvent = InputStream{{InputSample{5ms,
+                                                       {{.id = 0,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 1.0f,
+                                                         .y = 1.0f,
+                                                         .isResampled = false},
+                                                        {.id = 1,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 2.0f,
+                                                         .y = 2.0f,
+                                                         .isResampled = false}}},
+                                           InputSample{10ms,
+                                                       {{.id = 0,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 3.0f,
+                                                         .y = 3.0f,
+                                                         .isResampled = false},
+                                                        {.id = 1,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 4.0f,
+                                                         .y = 4.0f,
+                                                         .isResampled = false}}}},
+                                          AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
 } // namespace android
diff --git a/libs/vibrator/ExternalVibrationUtils.cpp b/libs/vibrator/ExternalVibrationUtils.cpp
index 54afb71..ca13afc 100644
--- a/libs/vibrator/ExternalVibrationUtils.cpp
+++ b/libs/vibrator/ExternalVibrationUtils.cpp
@@ -167,7 +167,7 @@
             }
         }
 
-        if (adaptiveScaleFactor != 1.0f) {
+        if (adaptiveScaleFactor >= 0 && adaptiveScaleFactor != 1.0f) {
             buffer[i] *= adaptiveScaleFactor;
         }
     }
diff --git a/libs/vibrator/include/vibrator/ExternalVibrationUtils.h b/libs/vibrator/include/vibrator/ExternalVibrationUtils.h
index 322a2ac..f0760fd 100644
--- a/libs/vibrator/include/vibrator/ExternalVibrationUtils.h
+++ b/libs/vibrator/include/vibrator/ExternalVibrationUtils.h
@@ -57,7 +57,9 @@
     return (mLevel == HapticLevel::NONE || mScaleFactor == 1.0f) && mAdaptiveScaleFactor == 1.0f;
 }
 
-bool isScaleMute() const { return mLevel == HapticLevel::MUTE || mScaleFactor == 0; }
+bool isScaleMute() const {
+    return mLevel == HapticLevel::MUTE || mScaleFactor == 0 || mAdaptiveScaleFactor == 0;
+}
 
 std::string toString() const {
     std::ostringstream os;
diff --git a/libs/vibrator/tests/ExternalVibrationUtilsTest.cpp b/libs/vibrator/tests/ExternalVibrationUtilsTest.cpp
index 7adc9c3..9369f80 100644
--- a/libs/vibrator/tests/ExternalVibrationUtilsTest.cpp
+++ b/libs/vibrator/tests/ExternalVibrationUtilsTest.cpp
@@ -188,9 +188,19 @@
 }
 
 TEST_F_WITH_FLAGS(
+        ExternalVibrationUtilsTest, TestScaleV2ToScaleFactorUndefinedUsesHapticLevel,
+        // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    constexpr float adaptiveScaleNone = 1.0f;
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = {1, -1, 0.8f, -0.38f};
+    scaleBuffer(HapticScale(HapticLevel::VERY_HIGH, -1.0f /* scaleFactor */, adaptiveScaleNone));
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
         ExternalVibrationUtilsTest, TestScaleV2ToScaleFactorIgnoresLevel,
-    // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
-    REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+        // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
     constexpr float adaptiveScaleNone = 1.0f;
 
     float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 1, -1, 1, -0.55f };
@@ -210,9 +220,23 @@
     EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
 }
 
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestAdaptiveScaleFactorUndefinedIsIgnoredLegacyScale,
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling),
+                                          ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = {1, -1, 0.79f, -0.39f};
+    scaleBuffer(HapticLevel::VERY_HIGH, -1.0f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
 TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestAdaptiveScaleFactorAppliedAfterLegacyScale,
                   REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling),
                                           ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    // Adaptive scale mutes vibration
+    float expectedMuted[TEST_BUFFER_LENGTH];
+    std::fill(std::begin(expectedMuted), std::end(expectedMuted), 0);
+    scaleBuffer(HapticLevel::VERY_HIGH, 0.0f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedMuted, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
     // Haptic level scale up then adaptive scale down
     float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 0.2, -0.2, 0.16f, -0.13f };
     scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */);
@@ -234,9 +258,23 @@
     EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
 }
 
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestAdaptiveScaleFactorUndefinedIgnoredFixedScale,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling)),
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = {1, -1, 0.79f, -0.39f};
+    scaleBuffer(HapticLevel::VERY_HIGH, -1.0f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
 TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestAdaptiveScaleFactorAppliedAfterFixedScale,
                   REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling)),
                   REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    // Adaptive scale mutes vibration
+    float expectedMuted[TEST_BUFFER_LENGTH];
+    std::fill(std::begin(expectedMuted), std::end(expectedMuted), 0);
+    scaleBuffer(HapticLevel::VERY_HIGH, 0.0f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedMuted, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
     // Haptic level scale up then adaptive scale down
     float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 0.2, -0.2, 0.16f, -0.07f };
     scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */);
@@ -259,9 +297,24 @@
 }
 
 TEST_F_WITH_FLAGS(
+        ExternalVibrationUtilsTest, TestAdaptiveScaleFactorUndefinedIgnoredScaleV2,
+        // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = {1, -1, 0.8f, -0.38f};
+    scaleBuffer(HapticLevel::VERY_HIGH, -1.0f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
         ExternalVibrationUtilsTest, TestAdaptiveScaleFactorAppliedAfterScaleV2,
         // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
         REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    // Adaptive scale mutes vibration
+    float expectedMuted[TEST_BUFFER_LENGTH];
+    std::fill(std::begin(expectedMuted), std::end(expectedMuted), 0);
+    scaleBuffer(HapticLevel::VERY_HIGH, 0.0f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedMuted, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
     // Haptic level scale up then adaptive scale down
     float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 0.2, -0.2, 0.15f, -0.07f };
     scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */);
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 625599a..397feda 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -511,8 +511,9 @@
     std::scoped_lock _l(mLock);
 
     dump += "PointerChoreographer:\n";
-    dump += StringPrintf("show touches: %s\n", mShowTouchesEnabled ? "true" : "false");
-    dump += StringPrintf("stylus pointer icon enabled: %s\n",
+    dump += StringPrintf(INDENT "Show Touches Enabled: %s\n",
+                         mShowTouchesEnabled ? "true" : "false");
+    dump += StringPrintf(INDENT "Stylus PointerIcon Enabled: %s\n",
                          mStylusPointerIconEnabled ? "true" : "false");
 
     dump += INDENT "MousePointerControllers:\n";
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 42a03c1..7bec94e 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -34,7 +34,9 @@
 #include <vector>
 
 #include "PointerControllerInterface.h"
+#include "TouchpadHardwareState.h"
 #include "VibrationElement.h"
+#include "include/gestures.h"
 
 // Maximum supported size of a vibration pattern.
 // Must be at least 2.
@@ -227,6 +229,9 @@
     // True to enable tap dragging on touchpads.
     bool touchpadTapDraggingEnabled;
 
+    // True if hardware state update notifications should be sent to the policy.
+    bool shouldNotifyTouchpadHardwareState;
+
     // True to enable a zone on the right-hand side of touchpads where clicks will be turned into
     // context (a.k.a. "right") clicks.
     bool touchpadRightClickZoneEnabled;
@@ -268,6 +273,7 @@
             touchpadNaturalScrollingEnabled(true),
             touchpadTapToClickEnabled(true),
             touchpadTapDraggingEnabled(false),
+            shouldNotifyTouchpadHardwareState(false),
             touchpadRightClickZoneEnabled(false),
             stylusButtonMotionEventsEnabled(true),
             stylusPointerIconEnabled(false) {}
@@ -363,6 +369,8 @@
 
     virtual std::vector<InputDeviceSensorInfo> getSensors(int32_t deviceId) = 0;
 
+    virtual std::optional<HardwareProperties> getTouchpadHardwareProperties(int32_t deviceId) = 0;
+
     /* Return true if the device can send input events to the specified display. */
     virtual bool canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) = 0;
 
@@ -454,6 +462,10 @@
      */
     virtual void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) = 0;
 
+    /* Sends the hardware state of a connected touchpad */
+    virtual void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
+                                             int32_t deviceId) = 0;
+
     /* Gets the keyboard layout for a particular input device. */
     virtual std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 2daf195..70f024e 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -725,6 +725,15 @@
     return count;
 }
 
+std::optional<HardwareProperties> InputDevice::getTouchpadHardwareProperties() {
+    std::optional<HardwareProperties> result = first_in_mappers<HardwareProperties>(
+            [](InputMapper& mapper) -> std::optional<HardwareProperties> {
+                return mapper.getTouchpadHardwareProperties();
+            });
+
+    return result;
+}
+
 void InputDevice::updateLedState(bool reset) {
     for_each_mapper([reset](InputMapper& mapper) { mapper.updateLedState(reset); });
 }
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index f0e53b5..a5b1249 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -33,6 +33,7 @@
 #include <utils/Thread.h>
 
 #include "InputDevice.h"
+#include "include/gestures.h"
 
 using android::base::StringPrintf;
 
@@ -817,6 +818,18 @@
     return device->getDeviceInfo().getSensors();
 }
 
+std::optional<HardwareProperties> InputReader::getTouchpadHardwareProperties(int32_t deviceId) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+
+    if (device == nullptr) {
+        return {};
+    }
+
+    return device->getTouchpadHardwareProperties();
+}
+
 bool InputReader::setLightColor(int32_t deviceId, int32_t lightId, int32_t color) {
     std::scoped_lock _l(mLock);
 
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 93785f6..021978d 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -141,6 +141,8 @@
 
     size_t getMapperCount();
 
+    std::optional<HardwareProperties> getTouchpadHardwareProperties();
+
     // construct and add a mapper to the input device
     template <class T, typename... Args>
     T& addMapper(int32_t eventHubId, Args... args) {
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 4f60a8a..2cc0a00 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -104,6 +104,8 @@
 
     std::vector<InputDeviceSensorInfo> getSensors(int32_t deviceId) override;
 
+    std::optional<HardwareProperties> getTouchpadHardwareProperties(int32_t deviceId) override;
+
     bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) override;
 
     bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) override;
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index c44c48c..627df7f 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -140,4 +140,7 @@
     dump += StringPrintf(INDENT4 "Tool Type: %s\n", ftl::enum_string(state.toolType).c_str());
 }
 
+std::optional<HardwareProperties> InputMapper::getTouchpadHardwareProperties() {
+    return std::nullopt;
+}
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index e5afcc7..75cc4bb 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -122,6 +122,8 @@
     virtual std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() { return std::nullopt; }
     virtual void updateLedState(bool reset) {}
 
+    virtual std::optional<HardwareProperties> getTouchpadHardwareProperties();
+
 protected:
     InputDeviceContext& mDeviceContext;
 
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 5c5fd3f..dbc2872 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -36,6 +36,7 @@
 #include <log/log_main.h>
 #include <stats_pull_atom_callback.h>
 #include <statslog.h>
+#include "InputReaderBase.h"
 #include "TouchCursorInputMapperCommon.h"
 #include "TouchpadInputMapper.h"
 #include "gestures/HardwareProperties.h"
@@ -251,7 +252,8 @@
     }
 
     mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD);
-    mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext));
+    mHardwareProperties = createHardwareProperties(deviceContext);
+    mGestureInterpreter->SetHardwareProperties(mHardwareProperties);
     // Even though we don't explicitly delete copy/move semantics, it's safe to
     // give away pointers to TouchpadInputMapper and its members here because
     // 1) mGestureInterpreter's lifecycle is determined by TouchpadInputMapper, and
@@ -372,6 +374,7 @@
                 .setBoolValues({config.touchpadTapDraggingEnabled});
         mPropertyProvider.getProperty("Button Right Click Zone Enable")
                 .setBoolValues({config.touchpadRightClickZoneEnabled});
+        mTouchpadHardwareStateNotificationsEnabled = config.shouldNotifyTouchpadHardwareState;
     }
     std::list<NotifyArgs> out;
     if ((!changes.any() && config.pointerCaptureRequest.isEnable()) ||
@@ -421,6 +424,9 @@
     }
     std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
     if (state) {
+        if (mTouchpadHardwareStateNotificationsEnabled) {
+            getPolicy()->notifyTouchpadHardwareState(*state, rawEvent.deviceId);
+        }
         updatePalmDetectionMetrics();
         return sendHardwareState(rawEvent.when, rawEvent.readTime, *state);
     } else {
@@ -493,4 +499,8 @@
     return mDisplayId;
 }
 
+std::optional<HardwareProperties> TouchpadInputMapper::getTouchpadHardwareProperties() {
+    return mHardwareProperties;
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index 8baa63e..a2c4be9 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -68,6 +68,8 @@
 
     std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
 
+    std::optional<HardwareProperties> getTouchpadHardwareProperties() override;
+
 private:
     void resetGestureInterpreter(nsecs_t when);
     explicit TouchpadInputMapper(InputDeviceContext& deviceContext,
@@ -92,6 +94,7 @@
     HardwareStateConverter mStateConverter;
     GestureConverter mGestureConverter;
     CapturedTouchpadEventConverter mCapturedEventConverter;
+    HardwareProperties mHardwareProperties;
 
     bool mPointerCaptured = false;
     bool mResettingInterpreter = false;
@@ -112,6 +115,10 @@
     std::optional<ui::LogicalDisplayId> mDisplayId;
 
     nsecs_t mGestureStartTime{0};
+
+    // True if hardware state update notifications is available for usage based on its feature flag
+    // and settings value.
+    bool mTouchpadHardwareStateNotificationsEnabled = false;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
index 66d62f8..148ca5a 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
@@ -28,6 +28,7 @@
 #include "accumulator/TouchButtonAccumulator.h"
 #include "include/TouchpadHardwareState.h"
 
+#include "TouchpadHardwareState.h"
 #include "include/gestures.h"
 
 namespace android {
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 6099c91..d77d539 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -65,6 +65,17 @@
     ASSERT_FALSE(mDeviceIdOfNotifiedStylusGesture);
 }
 
+void FakeInputReaderPolicy::assertTouchpadHardwareStateNotified() {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    const bool success =
+            mTouchpadHardwareStateNotified.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
+                return mTouchpadHardwareState.has_value();
+            });
+    ASSERT_TRUE(success) << "Timed out waiting for hardware state to be notified";
+}
+
 void FakeInputReaderPolicy::clearViewports() {
     mViewports.clear();
     mConfig.setDisplayViewports(mViewports);
@@ -234,6 +245,13 @@
     mDevicesChangedCondition.notify_all();
 }
 
+void FakeInputReaderPolicy::notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
+                                                        int32_t deviceId) {
+    std::scoped_lock lock(mLock);
+    mTouchpadHardwareState = schs;
+    mTouchpadHardwareStateNotified.notify_all();
+}
+
 std::shared_ptr<KeyCharacterMap> FakeInputReaderPolicy::getKeyboardLayoutOverlay(
         const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) {
     return nullptr;
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 94f1311..e5ba620 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -42,6 +42,7 @@
     void assertInputDevicesNotChanged();
     void assertStylusGestureNotified(int32_t deviceId);
     void assertStylusGestureNotNotified();
+    void assertTouchpadHardwareStateNotified();
 
     virtual void clearViewports();
     std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueId) const;
@@ -82,6 +83,8 @@
 private:
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
+    void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
+                                     int32_t deviceId) override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) override;
     std::string getDeviceAlias(const InputDeviceIdentifier&) override;
@@ -101,6 +104,9 @@
     std::condition_variable mStylusGestureNotifiedCondition;
     std::optional<DeviceId> mDeviceIdOfNotifiedStylusGesture GUARDED_BY(mLock){};
 
+    std::condition_variable mTouchpadHardwareStateNotified;
+    std::optional<SelfContainedHardwareState> mTouchpadHardwareState GUARDED_BY(mLock){};
+
     uint32_t mNextPointerCaptureSequenceNumber{0};
 };
 
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
index fc8a7da..ea69fff 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -172,4 +172,22 @@
     ASSERT_THAT(args, testing::IsEmpty());
 }
 
+TEST_F(TouchpadInputMapperTest, TouchpadHardwareState) {
+    mReaderConfiguration.shouldNotifyTouchpadHardwareState = true;
+    std::list<NotifyArgs> args =
+            mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                 InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+
+    args += process(EV_ABS, ABS_MT_TRACKING_ID, 1);
+    args += process(EV_KEY, BTN_TOUCH, 1);
+    setScanCodeState(KeyState::DOWN, {BTN_TOOL_FINGER});
+    args += process(EV_KEY, BTN_TOOL_FINGER, 1);
+    args += process(EV_ABS, ABS_MT_POSITION_X, 50);
+    args += process(EV_ABS, ABS_MT_POSITION_Y, 50);
+    args += process(EV_ABS, ABS_MT_PRESSURE, 1);
+    args += process(EV_SYN, SYN_REPORT, 0);
+
+    mFakePolicy->assertTouchpadHardwareStateNotified();
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index d552c19..3e4a19b 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -117,6 +117,10 @@
         return reader->getSensors(deviceId);
     }
 
+    std::optional<HardwareProperties> getTouchpadHardwareProperties(int32_t deviceId) {
+        return reader->getTouchpadHardwareProperties(deviceId);
+    }
+
     bool canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) {
         return reader->canDispatchToDisplay(deviceId, displayId);
     }
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index fea0d9a..ddc3310 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -281,6 +281,8 @@
     FuzzInputReaderPolicy(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {}
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override {}
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {}
+    void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
+                                     int32_t deviceId) override {}
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
             const std::optional<KeyboardLayoutInfo> layoutInfo) override {
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
index c77cdd4..748765a 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
@@ -31,10 +31,11 @@
 #include <utils/String8.h>
 #include <log/log.h>
 
-#include <hardware/hardware.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferItem.h>
 #include <gui/BufferQueue.h>
 #include <gui/Surface.h>
+#include <hardware/hardware.h>
 
 #include <ui/DebugUtils.h>
 #include <ui/GraphicBuffer.h>
@@ -48,10 +49,18 @@
 
 using ui::Dataspace;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+FramebufferSurface::FramebufferSurface(HWComposer& hwc, PhysicalDisplayId displayId,
+                                       const sp<IGraphicBufferProducer>& producer,
+                                       const sp<IGraphicBufferConsumer>& consumer,
+                                       const ui::Size& size, const ui::Size& maxSize)
+      : ConsumerBase(producer, consumer),
+#else
 FramebufferSurface::FramebufferSurface(HWComposer& hwc, PhysicalDisplayId displayId,
                                        const sp<IGraphicBufferConsumer>& consumer,
                                        const ui::Size& size, const ui::Size& maxSize)
       : ConsumerBase(consumer),
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
         mDisplayId(displayId),
         mMaxSize(maxSize),
         mCurrentBufferSlot(-1),
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
index 2728cf6..6ca64a2 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
@@ -20,6 +20,7 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+#include <com_android_graphics_libgui_flags.h>
 #include <compositionengine/DisplaySurface.h>
 #include <gui/BufferQueue.h>
 #include <gui/ConsumerBase.h>
@@ -40,9 +41,16 @@
 
 class FramebufferSurface : public ConsumerBase, public compositionengine::DisplaySurface {
 public:
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    FramebufferSurface(HWComposer& hwc, PhysicalDisplayId displayId,
+                       const sp<IGraphicBufferProducer>& producer,
+                       const sp<IGraphicBufferConsumer>& consumer, const ui::Size& size,
+                       const ui::Size& maxSize);
+#else
     FramebufferSurface(HWComposer& hwc, PhysicalDisplayId displayId,
                        const sp<IGraphicBufferConsumer>& consumer, const ui::Size& size,
                        const ui::Size& maxSize);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 
     virtual status_t beginFrame(bool mustRecompose);
     virtual status_t prepareFrame(CompositionType compositionType);
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index 4b5a68c..384f7b2 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -22,6 +22,7 @@
 
 #include <cinttypes>
 
+#include <com_android_graphics_libgui_flags.h>
 #include <ftl/enum.h>
 #include <ftl/flags.h>
 #include <gui/BufferItem.h>
@@ -51,7 +52,11 @@
                                              const sp<IGraphicBufferProducer>& bqProducer,
                                              const sp<IGraphicBufferConsumer>& bqConsumer,
                                              const std::string& name)
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+      : ConsumerBase(bqProducer, bqConsumer),
+#else
       : ConsumerBase(bqConsumer),
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
         mHwc(hwc),
         mDisplayId(displayId),
         mDisplayName(name),
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index f4335f3..d709530 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -18,6 +18,8 @@
 #undef LOG_TAG
 #define LOG_TAG "SurfaceFlinger"
 
+#include <android-base/logging.h>
+
 #include "LayerHierarchy.h"
 #include "LayerLog.h"
 #include "SwapErase.h"
@@ -485,6 +487,55 @@
     return it->second;
 }
 
+void LayerHierarchyBuilder::logSampledChildren(const LayerHierarchy& hierarchy) const {
+    LOG(ERROR) << "Dumping random sampling of child layers.";
+    int sampleSize = static_cast<int>(hierarchy.mChildren.size() / 100 + 1);
+    for (const auto& [child, variant] : hierarchy.mChildren) {
+        if (rand() % sampleSize == 0) {
+            LOG(ERROR) << "Child Layer: " << *(child->mLayer);
+        }
+    }
+}
+
+void LayerHierarchyBuilder::dumpLayerSample(const LayerHierarchy& root) const {
+    LOG(ERROR) << "Dumping layer keeping > 20 children alive:";
+    // If mLayer is nullptr, it will be skipped while traversing.
+    if (!root.mLayer && root.mChildren.size() > 20) {
+        LOG(ERROR) << "ROOT has " << root.mChildren.size() << " children";
+        logSampledChildren(root);
+    }
+    root.traverse([&](const LayerHierarchy& hierarchy, const auto&) -> bool {
+        if (hierarchy.mChildren.size() <= 20) {
+            return true;
+        }
+        // mLayer is ensured to be non-null. See LayerHierarchy::traverse.
+        const auto* layer = hierarchy.mLayer;
+        const auto childrenCount = hierarchy.mChildren.size();
+        LOG(ERROR) << "Layer " << *layer << " has " << childrenCount << " children";
+
+        const auto* parent = hierarchy.mParent;
+        while (parent != nullptr) {
+            if (!parent->mLayer) break;
+            LOG(ERROR) << "Parent Layer: " << *(parent->mLayer);
+            parent = parent->mParent;
+        }
+
+        logSampledChildren(hierarchy);
+        // Stop traversing.
+        return false;
+    });
+    LOG(ERROR) << "Dumping random sampled layers.";
+    size_t numLayers = 0;
+    root.traverse([&](const LayerHierarchy& hierarchy, const auto&) -> bool {
+        if (hierarchy.mLayer) numLayers++;
+        if ((rand() % 20 == 13) && hierarchy.mLayer) {
+            LOG(ERROR) << "Layer: " << *(hierarchy.mLayer);
+        }
+        return true;
+    });
+    LOG(ERROR) << "Total layer count: " << numLayers;
+}
+
 const LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::ROOT =
         {.id = UNASSIGNED_LAYER_ID, .variant = LayerHierarchy::Attached};
 
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index d023f9e..47d0041 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -211,8 +211,11 @@
     const LayerHierarchy& getHierarchy() const;
     const LayerHierarchy& getOffscreenHierarchy() const;
     std::string getDebugString(uint32_t layerId, uint32_t depth = 0) const;
+    void dumpLayerSample(const LayerHierarchy& layerHierarchy) const;
 
 private:
+    void logSampledChildren(const LayerHierarchy& hierarchy) const;
+
     void onLayerAdded(RequestedLayerState* layer);
     void attachToParent(LayerHierarchy*);
     void detachFromParent(LayerHierarchy*);
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index c874db3..ac15b92 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -1265,6 +1265,12 @@
     }
 }
 
+void LayerSnapshotBuilder::forEachSnapshot(const ConstVisitor& visitor) const {
+    for (auto& snapshot : mSnapshots) {
+        visitor(*snapshot);
+    }
+}
+
 void LayerSnapshotBuilder::forEachInputSnapshot(const ConstVisitor& visitor) const {
     for (int i = mNumInterestingSnapshots - 1; i >= 0; i--) {
         LayerSnapshot& snapshot = *mSnapshots[(size_t)i];
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
index 207e23a..486cb33 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -91,6 +91,9 @@
     // snapshots in z-order
     void forEachSnapshot(const Visitor& visitor, const ConstPredicate& predicate);
 
+    // Visit each snapshot
+    void forEachSnapshot(const ConstVisitor& visitor) const;
+
     // Visit each snapshot interesting to input reverse z-order
     void forEachInputSnapshot(const ConstVisitor& visitor) const;
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index abdf92c..636f7bd 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -317,7 +317,6 @@
     if (mRemovedFromDrawingState) {
         mRemovedFromDrawingState = false;
         mFlinger->mScheduler->registerLayer(this, FrameRateCompatibility::Default);
-        mFlinger->removeFromOffscreenLayers(this);
     }
 
     for (const auto& child : mCurrentChildren) {
@@ -1350,14 +1349,6 @@
     return usage;
 }
 
-void Layer::updateTransformHint(ui::Transform::RotationFlags transformHint) {
-    if (mFlinger->mDebugDisableTransformHint || transformHint & ui::Transform::ROT_INVALID) {
-        transformHint = ui::Transform::ROT_0;
-    }
-
-    setTransformHintLegacy(transformHint);
-}
-
 // ----------------------------------------------------------------------------
 // debugging
 // ----------------------------------------------------------------------------
@@ -4001,13 +3992,6 @@
     return mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer() : nullptr;
 }
 
-void Layer::setTransformHintLegacy(ui::Transform::RotationFlags displayTransformHint) {
-    mTransformHintLegacy = getFixedTransformHint();
-    if (mTransformHintLegacy == ui::Transform::ROT_INVALID) {
-        mTransformHintLegacy = displayTransformHint;
-    }
-}
-
 const std::shared_ptr<renderengine::ExternalTexture>& Layer::getExternalTexture() const {
     return mBufferInfo.mBuffer;
 }
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 948c62d..f6eed63 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -687,10 +687,6 @@
      */
     void addToCurrentState();
 
-    /*
-     * Sets display transform hint on BufferLayerConsumer.
-     */
-    void updateTransformHint(ui::Transform::RotationFlags);
     inline const State& getDrawingState() const { return mDrawingState; }
     inline State& getDrawingState() { return mDrawingState; }
 
@@ -1212,13 +1208,11 @@
 
     bool findInHierarchy(const sp<Layer>&);
 
-    void setTransformHintLegacy(ui::Transform::RotationFlags);
     void releasePreviousBuffer();
     void resetDrawingStateBufferInfo();
 
     // Transform hint provided to the producer. This must be accessed holding
     // the mStateLock.
-    ui::Transform::RotationFlags mTransformHintLegacy = ui::Transform::ROT_0;
     std::optional<ui::Transform::RotationFlags> mTransformHint = std::nullopt;
 
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index a1a60e3..dbc458c 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -595,6 +595,12 @@
         return true;
     }
 
+    if (FlagManager::getInstance().view_set_requested_frame_rate_mrr() &&
+        category == FrameRateCategory::NoPreference && vote.rate.isValid() &&
+        vote.type == FrameRateCompatibility::ExactOrMultiple) {
+        return true;
+    }
+
     return false;
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 727541e..566bb8e 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -122,6 +122,12 @@
 
     demotePacesetterDisplay(kPromotionParams);
     promotePacesetterDisplay(pacesetterId, kPromotionParams);
+
+    // Cancel the pending refresh rate change, if any, before updating the phase configuration.
+    mVsyncModulator->cancelRefreshRateChange();
+
+    mVsyncConfiguration->reset();
+    updatePhaseConfiguration(pacesetterSelectorPtr()->getActiveMode().fps);
 }
 
 void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
@@ -488,14 +494,6 @@
                    refreshRate.getPeriod());
 }
 
-void Scheduler::resetPhaseConfiguration(Fps refreshRate) {
-    // Cancel the pending refresh rate change, if any, before updating the phase configuration.
-    mVsyncModulator->cancelRefreshRateChange();
-
-    mVsyncConfiguration->reset();
-    updatePhaseConfiguration(refreshRate);
-}
-
 void Scheduler::setActiveDisplayPowerModeForRefreshRateStats(hal::PowerMode powerMode) {
     mRefreshRateStats->setPowerMode(powerMode);
 }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 9b32fa9..88f0e94 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -188,7 +188,6 @@
     }
 
     void updatePhaseConfiguration(Fps);
-    void resetPhaseConfiguration(Fps) REQUIRES(kMainThreadContext);
 
     const VsyncConfiguration& getVsyncConfiguration() const { return *mVsyncConfiguration; }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index e8d64d9..0aad282 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -40,6 +40,7 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/PermissionCache.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <com_android_graphics_surfaceflinger_flags.h>
 #include <common/FlagManager.h>
 #include <common/trace.h>
@@ -2520,12 +2521,18 @@
     }
 
     updateLayerHistory(latchTime);
-    mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
-        if (mLayersIdsWithQueuedFrames.find(snapshot.path.id) == mLayersIdsWithQueuedFrames.end())
-            return;
-        Region visibleReg;
-        visibleReg.set(snapshot.transformedBoundsWithoutTransparentRegion);
-        invalidateLayerStack(snapshot.outputFilter, visibleReg);
+    mLayerSnapshotBuilder.forEachSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+        // update output dirty region if we have a queued buffer that is visible or a snapshot
+        // recently became invisible
+        // TODO(b/360050020) investigate if we need to update dirty region when layer color changes
+        if ((snapshot.isVisible &&
+             (mLayersIdsWithQueuedFrames.find(snapshot.path.id) !=
+              mLayersIdsWithQueuedFrames.end())) ||
+            (!snapshot.isVisible && snapshot.changes.test(Changes::Visibility))) {
+            Region visibleReg;
+            visibleReg.set(snapshot.transformedBoundsWithoutTransparentRegion);
+            invalidateLayerStack(snapshot.outputFilter, visibleReg);
+        }
     });
 
     for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) {
@@ -3722,11 +3729,20 @@
                  state.surface.get());
         const auto displayId = PhysicalDisplayId::tryCast(compositionDisplay->getId());
         LOG_FATAL_IF(!displayId);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        const auto frameBufferSurface =
+                sp<FramebufferSurface>::make(getHwComposer(), *displayId, bqProducer, bqConsumer,
+                                             state.physical->activeMode->getResolution(),
+                                             ui::Size(maxGraphicsWidth, maxGraphicsHeight));
+        displaySurface = frameBufferSurface;
+        producer = frameBufferSurface->getSurface()->getIGraphicBufferProducer();
+#else
         displaySurface =
                 sp<FramebufferSurface>::make(getHwComposer(), *displayId, bqConsumer,
                                              state.physical->activeMode->getResolution(),
                                              ui::Size(maxGraphicsWidth, maxGraphicsHeight));
         producer = bqProducer;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     }
 
     LOG_FATAL_IF(!displaySurface);
@@ -3830,11 +3846,8 @@
                 setPowerModeInternal(display, hal::PowerMode::ON);
             }
 
-            // TODO(b/175678251) Call a listener instead.
-            if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) {
-                const Fps refreshRate =
-                        mDisplayModeController.getActiveMode(display->getPhysicalId()).fps;
-                mScheduler->resetPhaseConfiguration(refreshRate);
+            if (display->getPhysicalId() == mActiveDisplayId) {
+                onActiveDisplayChangedLocked(nullptr, *display);
             }
         }
         return;
@@ -3926,51 +3939,6 @@
         mUpdateInputInfo = true;
     }
 
-    // Update transform hint.
-    if (transactionFlags & (eTransformHintUpdateNeeded | eDisplayTransactionNeeded)) {
-        // Layers and/or displays have changed, so update the transform hint for each layer.
-        //
-        // NOTE: we do this here, rather than when presenting the display so that
-        // the hint is set before we acquire a buffer from the surface texture.
-        //
-        // NOTE: layer transactions have taken place already, so we use their
-        // drawing state. However, SurfaceFlinger's own transaction has not
-        // happened yet, so we must use the current state layer list
-        // (soon to become the drawing state list).
-        //
-        sp<const DisplayDevice> hintDisplay;
-        ui::LayerStack layerStack;
-
-        mCurrentState.traverse([&](Layer* layer) REQUIRES(mStateLock) {
-            // NOTE: we rely on the fact that layers are sorted by
-            // layerStack first (so we don't have to traverse the list
-            // of displays for every layer).
-            if (const auto filter = layer->getOutputFilter(); layerStack != filter.layerStack) {
-                layerStack = filter.layerStack;
-                hintDisplay = nullptr;
-
-                // Find the display that includes the layer.
-                for (const auto& [token, display] : mDisplays) {
-                    if (!display->getCompositionDisplay()->includesLayer(filter)) {
-                        continue;
-                    }
-
-                    // Pick the primary display if another display mirrors the layer.
-                    if (hintDisplay) {
-                        hintDisplay = nullptr;
-                        break;
-                    }
-
-                    hintDisplay = display;
-                }
-            }
-
-            if (hintDisplay) {
-                layer->updateTransformHint(hintDisplay->getTransformHint());
-            }
-        });
-    }
-
     if (mLayersAdded) {
         mLayersAdded = false;
         // Layers have been added.
@@ -3984,14 +3952,6 @@
         mLayersRemoved = false;
         mVisibleRegionsDirty = true;
         mUpdateInputInfo = true;
-        mDrawingState.traverseInZOrder([&](Layer* layer) {
-            if (mLayersPendingRemoval.indexOf(sp<Layer>::fromExisting(layer)) >= 0) {
-                // this layer is not visible anymore
-                Region visibleReg;
-                visibleReg.set(layer->getScreenBounds());
-                invalidateLayerStack(layer->getOutputFilter(), visibleReg);
-            }
-        });
     }
 
     if (transactionFlags & eInputInfoUpdateNeeded) {
@@ -4409,13 +4369,6 @@
                 l->setIsAtRoot(false);
                 mCurrentState.layersSortedByZ.remove(l);
             }
-
-            // If the layer has been removed and has no parent, then it will not be reachable
-            // when traversing layers on screen. Add the layer to the offscreenLayers set to
-            // ensure we can copy its current to drawing state.
-            if (!l->getParent()) {
-                mOffscreenLayers.emplace(l.get());
-            }
         }
         mLayersPendingRemoval.clear();
     }
@@ -4429,7 +4382,6 @@
         }
     }
 
-    commitOffscreenLayers();
     if (mLayerMirrorRoots.size() > 0) {
         std::deque<Layer*> pendingUpdates;
         pendingUpdates.insert(pendingUpdates.end(), mLayerMirrorRoots.begin(),
@@ -4451,17 +4403,6 @@
     }
 }
 
-void SurfaceFlinger::commitOffscreenLayers() {
-    for (Layer* offscreenLayer : mOffscreenLayers) {
-        offscreenLayer->traverse(LayerVector::StateSet::Drawing, [](Layer* layer) {
-            if (layer->clearTransactionFlags(eTransactionNeeded)) {
-                layer->doTransaction(0);
-                layer->commitChildList();
-            }
-        });
-    }
-}
-
 void SurfaceFlinger::invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty) {
     for (const auto& [token, displayDevice] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
         auto display = displayDevice->getCompositionDisplay();
@@ -4475,65 +4416,26 @@
                                         const sp<Layer>& layer, const wp<Layer>& parent,
                                         uint32_t* outTransformHint) {
     if (mNumLayers >= MAX_LAYERS) {
+        static std::atomic<nsecs_t> lasttime{0};
+        nsecs_t now = systemTime();
+        if (lasttime != 0 && ns2s(now - lasttime.load()) < 10) {
+            ALOGE("AddClientLayer already dumped 10s before");
+            return NO_MEMORY;
+        } else {
+            lasttime = now;
+        }
+
         ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
               MAX_LAYERS);
-        static_cast<void>(mScheduler->schedule([=, this] {
-            ALOGE("Dumping layer keeping > 20 children alive:");
-            bool leakingParentLayerFound = false;
-            mDrawingState.traverse([&](Layer* layer) {
-                if (leakingParentLayerFound) {
-                    return;
-                }
-                if (layer->getChildrenCount() > 20) {
-                    leakingParentLayerFound = true;
-                    sp<Layer> parent = sp<Layer>::fromExisting(layer);
-                    while (parent) {
-                        ALOGE("Parent Layer: %s%s", parent->getName().c_str(),
-                              (parent->isHandleAlive() ? "handleAlive" : ""));
-                        parent = parent->getParent();
-                    }
-                    // Sample up to 100 layers
-                    ALOGE("Dumping random sampling of child layers total(%zu): ",
-                          layer->getChildrenCount());
-                    int sampleSize = (layer->getChildrenCount() / 100) + 1;
-                    layer->traverseChildren([&](Layer* layer) {
-                        if (rand() % sampleSize == 0) {
-                            ALOGE("Child Layer: %s%s", layer->getName().c_str(),
-                                  (layer->isHandleAlive() ? "handleAlive" : ""));
-                        }
-                    });
-                }
-            });
-
-            int numLayers = 0;
-            mDrawingState.traverse([&](Layer* layer) { numLayers++; });
-
-            ALOGE("Dumping random sampling of on-screen layers total(%u):", numLayers);
-            mDrawingState.traverse([&](Layer* layer) {
-                // Aim to dump about 200 layers to avoid totally trashing
-                // logcat. On the other hand, if there really are 4096 layers
-                // something has gone totally wrong its probably the most
-                // useful information in logcat.
-                if (rand() % 20 == 13) {
-                    ALOGE("Layer: %s%s", layer->getName().c_str(),
-                          (layer->isHandleAlive() ? "handleAlive" : ""));
-                    std::this_thread::sleep_for(std::chrono::milliseconds(5));
-                }
-            });
-            ALOGE("Dumping random sampling of off-screen layers total(%zu): ",
-                  mOffscreenLayers.size());
-            for (Layer* offscreenLayer : mOffscreenLayers) {
-                if (rand() % 20 == 13) {
-                    ALOGE("Offscreen-layer: %s%s", offscreenLayer->getName().c_str(),
-                          (offscreenLayer->isHandleAlive() ? "handleAlive" : ""));
-                    std::this_thread::sleep_for(std::chrono::milliseconds(5));
-                }
-            }
+        static_cast<void>(mScheduler->schedule([&]() FTL_FAKE_GUARD(kMainThreadContext) {
+            ALOGE("Dumping on-screen layers.");
+            mLayerHierarchyBuilder.dumpLayerSample(mLayerHierarchyBuilder.getHierarchy());
+            ALOGE("Dumping off-screen layers.");
+            mLayerHierarchyBuilder.dumpLayerSample(mLayerHierarchyBuilder.getOffscreenHierarchy());
         }));
         return NO_MEMORY;
     }
 
-    layer->updateTransformHint(mActiveDisplayTransformHint);
     if (outTransformHint) {
         *outTransformHint = mActiveDisplayTransformHint;
     }
@@ -5748,7 +5650,7 @@
     sTimestamp = now;
 
     SFTRACE_CALL();
-    mDrawingState.traverse([&](Layer* layer) { layer->logFrameStats(); });
+    traverseLegacyLayers([&](Layer* layer) { layer->logFrameStats(); });
 }
 
 void SurfaceFlinger::appendSfConfigString(std::string& result) const {
@@ -6051,20 +5953,6 @@
             .get();
 }
 
-void SurfaceFlinger::dumpOffscreenLayers(std::string& result) {
-    auto future = mScheduler->schedule([this] {
-        std::string result;
-        for (Layer* offscreenLayer : mOffscreenLayers) {
-            offscreenLayer->traverse(LayerVector::StateSet::Drawing,
-                                     [&](Layer* layer) { layer->dumpOffscreenDebugInfo(result); });
-        }
-        return result;
-    });
-
-    result.append("Offscreen Layers:\n");
-    result.append(future.get());
-}
-
 void SurfaceFlinger::dumpHwcLayersMinidump(std::string& result) const {
     for (const auto& [token, display] : mDisplays) {
         const auto displayId = HalDisplayId::tryCast(display->getId());
@@ -8006,7 +7894,6 @@
 
 void SurfaceFlinger::onLayerDestroyed(Layer* layer) {
     mNumLayers--;
-    removeHierarchyFromOffscreenLayers(layer);
     if (!layer->isRemovedFromCurrentState()) {
         mScheduler->deregisterLayer(layer);
     }
@@ -8020,24 +7907,6 @@
     scheduleCommit(FrameHint::kActive);
 }
 
-// WARNING: ONLY CALL THIS FROM LAYER DTOR
-// Here we add children in the current state to offscreen layers and remove the
-// layer itself from the offscreen layer list.  Since
-// this is the dtor, it is safe to access the current state.  This keeps us
-// from dangling children layers such that they are not reachable from the
-// Drawing state nor the offscreen layer list
-// See b/141111965
-void SurfaceFlinger::removeHierarchyFromOffscreenLayers(Layer* layer) {
-    for (auto& child : layer->getCurrentChildren()) {
-        mOffscreenLayers.emplace(child.get());
-    }
-    mOffscreenLayers.erase(layer);
-}
-
-void SurfaceFlinger::removeFromOffscreenLayers(Layer* layer) {
-    mOffscreenLayers.erase(layer);
-}
-
 status_t SurfaceFlinger::setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor,
                                                  float lightPosY, float lightPosZ,
                                                  float lightRadius) {
@@ -8209,20 +8078,6 @@
     } else {
         parent->addChild(layer);
     }
-
-    ui::LayerStack layerStack = layer->getLayerStack(LayerVector::StateSet::Current);
-    sp<const DisplayDevice> hintDisplay;
-    // Find the display that includes the layer.
-    for (const auto& [token, display] : mDisplays) {
-        if (display->getLayerStack() == layerStack) {
-            hintDisplay = display;
-            break;
-        }
-    }
-
-    if (hintDisplay) {
-        layer->updateTransformHint(hintDisplay->getTransformHint());
-    }
 }
 
 void SurfaceFlinger::sample() {
@@ -8267,8 +8122,6 @@
     mActiveDisplayId = activeDisplay.getPhysicalId();
     activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true);
 
-    mScheduler->resetPhaseConfiguration(mDisplayModeController.getActiveMode(mActiveDisplayId).fps);
-
     // TODO(b/255635711): Check for pending mode changes on other displays.
     mScheduler->setModeChangePending(false);
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 651f2d3..282c8cf 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -292,9 +292,6 @@
     void onLayerDestroyed(Layer*);
     void onLayerUpdate();
 
-    void removeHierarchyFromOffscreenLayers(Layer* layer);
-    void removeFromOffscreenLayers(Layer* layer);
-
     // Called when all clients have released all their references to
     // this layer. The layer may still be kept alive by its parents but
     // the client can no longer modify this layer directly.
@@ -824,8 +821,6 @@
     // Clears and returns the masked bits.
     uint32_t clearTransactionFlags(uint32_t mask);
 
-    void commitOffscreenLayers();
-
     static LatchUnsignaledConfig getLatchUnsignaledConfig();
     bool shouldLatchUnsignaled(const layer_state_t&, size_t numStates, bool firstTransaction) const;
     bool applyTransactionsLocked(std::vector<TransactionState>& transactions, VsyncId)
@@ -1147,7 +1142,6 @@
     void dumpHwc(std::string& result) const;
     perfetto::protos::LayersProto dumpProtoFromMainThread(
             uint32_t traceFlags = LayerTracing::TRACE_ALL) EXCLUDES(mStateLock);
-    void dumpOffscreenLayers(std::string& result) EXCLUDES(mStateLock);
     void dumpPlannerInfo(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
 
     status_t doDump(int fd, const DumpArgs& args, bool asProto);
@@ -1393,12 +1387,6 @@
     // Flag used to set override desired display mode from backdoor
     bool mDebugDisplayModeSetByBackdoor = false;
 
-    // A set of layers that have no parent so they are not drawn on screen.
-    // Should only be accessed by the main thread.
-    // The Layer pointer is removed from the set when the destructor is called so there shouldn't
-    // be any issues with a raw pointer referencing an invalid object.
-    std::unordered_set<Layer*> mOffscreenLayers;
-
     BufferCountTracker mBufferCountTracker;
 
     std::unordered_map<DisplayId, sp<HdrLayerInfoReporter>> mHdrLayerInfoListeners
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index a56bb51..07c720f 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -119,6 +119,7 @@
     DUMP_READ_ONLY_FLAG(connected_display);
     DUMP_READ_ONLY_FLAG(enable_small_area_detection);
     DUMP_READ_ONLY_FLAG(frame_rate_category_mrr);
+    DUMP_READ_ONLY_FLAG(view_set_requested_frame_rate_mrr);
     DUMP_READ_ONLY_FLAG(misc1);
     DUMP_READ_ONLY_FLAG(vrr_config);
     DUMP_READ_ONLY_FLAG(hotplug2);
@@ -222,6 +223,8 @@
 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")
+FLAG_MANAGER_READ_ONLY_FLAG(view_set_requested_frame_rate_mrr,
+                            "debug.sf.view_set_requested_frame_rate_mrr")
 FLAG_MANAGER_READ_ONLY_FLAG(misc1, "")
 FLAG_MANAGER_READ_ONLY_FLAG(vrr_config, "debug.sf.enable_vrr_config")
 FLAG_MANAGER_READ_ONLY_FLAG(hotplug2, "")
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 8799295..a4b4a2b 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -56,6 +56,7 @@
     /// Trunk stable readonly flags ///
     bool connected_display() const;
     bool frame_rate_category_mrr() const;
+    bool view_set_requested_frame_rate_mrr() const;
     bool enable_small_area_detection() const;
     bool misc1() const;
     bool vrr_config() const;
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index 919ec17..886167e 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -126,6 +126,14 @@
 } # override_trusted_overlay
 
 flag {
+    name: "view_set_requested_frame_rate_mrr"
+    namespace: "core_graphics"
+    description: "Enable to use frame rate category NoPreference with fixed frame rate vote on MRR devices"
+    bug: "352206100"
+    is_fixed_read_only: true
+} # view_set_requested_frame_rate_mrr
+
+flag {
   name: "vrr_bugfix_24q4"
   namespace: "core_graphics"
   description: "bug fixes for VRR"
diff --git a/services/surfaceflinger/tests/BufferGenerator.cpp b/services/surfaceflinger/tests/BufferGenerator.cpp
index d74bd55..efab7b8 100644
--- a/services/surfaceflinger/tests/BufferGenerator.cpp
+++ b/services/surfaceflinger/tests/BufferGenerator.cpp
@@ -42,8 +42,13 @@
      * through saved callback. */
     class BufferListener : public ConsumerBase::FrameAvailableListener {
     public:
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        BufferListener(sp<BufferItemConsumer> consumer, BufferCallback callback)
+#else
         BufferListener(sp<IGraphicBufferConsumer> consumer, BufferCallback callback)
-              : mConsumer(consumer), mCallback(callback) {}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+              : mConsumer(consumer), mCallback(callback) {
+        }
 
         void onFrameAvailable(const BufferItem& /*item*/) {
             BufferItem item;
@@ -55,7 +60,11 @@
         }
 
     private:
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        sp<BufferItemConsumer> mConsumer;
+#else
         sp<IGraphicBufferConsumer> mConsumer;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
         BufferCallback mCallback;
     };
 
@@ -63,6 +72,16 @@
      * queue. */
     void initialize(uint32_t width, uint32_t height, android_pixel_format_t format,
                     BufferCallback callback) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        mBufferItemConsumer = sp<BufferItemConsumer>::make(GraphicBuffer::USAGE_HW_TEXTURE);
+        mBufferItemConsumer->setDefaultBufferSize(width, height);
+        mBufferItemConsumer->setDefaultBufferFormat(format);
+
+        mListener = sp<BufferListener>::make(mBufferItemConsumer, callback);
+        mBufferItemConsumer->setFrameAvailableListener(mListener);
+
+        mSurface = mBufferItemConsumer->getSurface();
+#else
         sp<IGraphicBufferProducer> producer;
         sp<IGraphicBufferConsumer> consumer;
         BufferQueue::createBufferQueue(&producer, &consumer);
@@ -77,6 +96,7 @@
         mBufferItemConsumer->setFrameAvailableListener(mListener);
 
         mSurface = sp<Surface>::make(producer, true);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     }
 
     /* Used by Egl manager. The surface is never displayed. */
diff --git a/services/surfaceflinger/tests/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h
index af3cb9a..67a5247 100644
--- a/services/surfaceflinger/tests/TransactionTestHarnesses.h
+++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h
@@ -16,6 +16,7 @@
 #ifndef ANDROID_TRANSACTION_TEST_HARNESSES
 #define ANDROID_TRANSACTION_TEST_HARNESSES
 
+#include <com_android_graphics_libgui_flags.h>
 #include <common/FlagManager.h>
 #include <ui/DisplayState.h>
 
@@ -51,6 +52,16 @@
                 const ui::Size& resolution = displayMode.resolution;
 
                 sp<IBinder> vDisplay;
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+                sp<BufferItemConsumer> itemConsumer = sp<BufferItemConsumer>::make(
+                        // Sample usage bits from screenrecord
+                        GRALLOC_USAGE_HW_VIDEO_ENCODER | GRALLOC_USAGE_SW_READ_OFTEN);
+                sp<BufferListener> listener = sp<BufferListener>::make(this);
+                itemConsumer->setFrameAvailableListener(listener);
+                itemConsumer->setName(String8("Virtual disp consumer"));
+                itemConsumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
+#else
                 sp<IGraphicBufferProducer> producer;
                 sp<IGraphicBufferConsumer> consumer;
                 sp<BufferItemConsumer> itemConsumer;
@@ -65,6 +76,7 @@
                                                                     GRALLOC_USAGE_SW_READ_OFTEN);
                 sp<BufferListener> listener = sp<BufferListener>::make(this);
                 itemConsumer->setFrameAvailableListener(listener);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 
                 static const std::string kDisplayName("VirtualDisplay");
                 vDisplay = SurfaceComposerClient::createVirtualDisplay(kDisplayName,
@@ -76,7 +88,12 @@
                         SurfaceComposerClient::getDefault()->mirrorDisplay(displayId);
 
                 SurfaceComposerClient::Transaction t;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+                t.setDisplaySurface(vDisplay,
+                                    itemConsumer->getSurface()->getIGraphicBufferProducer());
+#else
                 t.setDisplaySurface(vDisplay, producer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
                 t.setDisplayProjection(vDisplay, displayState.orientation,
                                        Rect(displayState.layerStackSpaceRect), Rect(resolution));
                 if (FlagManager::getInstance().ce_fence_promise()) {
diff --git a/services/surfaceflinger/tests/VirtualDisplay_test.cpp b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
index cd66dd2..d69378c 100644
--- a/services/surfaceflinger/tests/VirtualDisplay_test.cpp
+++ b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
@@ -27,6 +27,12 @@
 class VirtualDisplayTest : public ::testing::Test {
 protected:
     void SetUp() override {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        mGLConsumer = sp<GLConsumer>::make(GLConsumer::TEXTURE_EXTERNAL, true, false, false);
+        mGLConsumer->setName(String8("Virtual disp consumer"));
+        mGLConsumer->setDefaultBufferSize(100, 100);
+        mProducer = mGLConsumer->getSurface()->getIGraphicBufferProducer();
+#else
         sp<IGraphicBufferConsumer> consumer;
 
         BufferQueue::createBufferQueue(&mProducer, &consumer);
@@ -34,6 +40,7 @@
         consumer->setDefaultBufferSize(100, 100);
 
         mGLConsumer = sp<GLConsumer>::make(consumer, GLConsumer::TEXTURE_EXTERNAL, true, false);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     }
 
     sp<IGraphicBufferProducer> mProducer;
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 52bb07a..7e84408 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -892,6 +892,50 @@
     EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
 }
 
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitVoteWithFixedSourceAndNoPreferenceCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
+    SET_FLAG_FOR_TEST(flags::view_set_requested_frame_rate_mrr, true);
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    setFrameRate(1, (45.6_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    // There are 2 LayerRequirement's due to the frame rate category.
+    ASSERT_EQ(2u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    // First LayerRequirement is the layer's category specification
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::NoPreference, summarizeLayerHistory(time)[0].frameRateCategory);
+
+    // Second LayerRequirement is the frame rate specification
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              summarizeLayerHistory(time)[1].vote);
+    EXPECT_EQ(45.6_Hz, summarizeLayerHistory(time)[1].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[1].frameRateCategory);
+
+    // layer became infrequent, but the vote stays
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(2u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::NoPreference, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
 TEST_F(LayerHistoryIntegrationTest, multipleLayers) {
     auto layer1 = createLegacyAndFrontedEndLayer(1);
     auto layer2 = createLegacyAndFrontedEndLayer(2);