Merge "libbinder: Add log when FDs aren't supported in RpcSession" into main am: f5883db738 am: 923c710a6d

Original change: https://android-review.googlesource.com/c/platform/frameworks/native/+/3156117

Change-Id: I6d42f7acabd898cf6f3dd5fc655d2f0649b0704a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/include/input/Input.h b/include/input/Input.h
index ec08cdd..17672d1 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -25,6 +25,7 @@
 #include <android/input.h>
 #ifdef __linux__
 #include <android/os/IInputConstants.h>
+#include <android/os/MotionEventFlag.h>
 #endif
 #include <android/os/PointerIconType.h>
 #include <math.h>
@@ -69,15 +70,17 @@
      * actual intent.
      */
     AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+            static_cast<int32_t>(android::os::MotionEventFlag::WINDOW_IS_PARTIALLY_OBSCURED),
+
     AMOTION_EVENT_FLAG_HOVER_EXIT_PENDING =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING,
+            static_cast<int32_t>(android::os::MotionEventFlag::HOVER_EXIT_PENDING),
+
     /**
      * This flag indicates that the event has been generated by a gesture generator. It
      * provides a hint to the GestureDetector to not apply any touch slop.
      */
     AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE,
+            static_cast<int32_t>(android::os::MotionEventFlag::IS_GENERATED_GESTURE),
 
     /**
      * This flag indicates that the event will not cause a focus change if it is directed to an
@@ -86,27 +89,27 @@
      * into focus.
      */
     AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE,
+            static_cast<int32_t>(android::os::MotionEventFlag::NO_FOCUS_CHANGE),
 
     /**
      * This event was generated or modified by accessibility service.
      */
     AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT =
-            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+            static_cast<int32_t>(android::os::MotionEventFlag::IS_ACCESSIBILITY_EVENT),
 
     AMOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS =
-            android::os::IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS,
+            static_cast<int32_t>(android::os::MotionEventFlag::TARGET_ACCESSIBILITY_FOCUS),
 
     /* Motion event is inconsistent with previously sent motion events. */
-    AMOTION_EVENT_FLAG_TAINTED = android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED,
+    AMOTION_EVENT_FLAG_TAINTED = static_cast<int32_t>(android::os::MotionEventFlag::TAINTED),
 
     /** Private flag, not used in Java. */
     AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION =
-            android::os::IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION,
+            static_cast<int32_t>(android::os::MotionEventFlag::PRIVATE_FLAG_SUPPORTS_ORIENTATION),
 
     /** Private flag, not used in Java. */
-    AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = android::os::IInputConstants::
-            MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION,
+    AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = static_cast<int32_t>(
+            android::os::MotionEventFlag::PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION),
 
     /** Mask for all private flags that are not used in Java. */
     AMOTION_EVENT_PRIVATE_FLAG_MASK = AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
@@ -193,6 +196,13 @@
 #define MAX_POINTER_ID 31
 
 /*
+ * Number of high resolution mouse scroll units for one detent (mouse wheel click), as defined in
+ * evdev. This is relevant when an input device is emitting REL_WHEEL_HI_RES or REL_HWHEEL_HI_RES
+ * events.
+ */
+constexpr int32_t kEvdevMouseHighResScrollUnitsPerDetent = 120;
+
+/*
  * Declare a concrete type for the NDK's input event forward declaration.
  */
 struct AInputEvent {
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
index 9e48b08..c7b1970 100644
--- a/include/input/InputConsumerNoResampling.h
+++ b/include/input/InputConsumerNoResampling.h
@@ -24,7 +24,7 @@
 /**
  * An interface to receive batched input events. Even if you don't want batching, you still have to
  * use this interface, and some of the events will be batched if your implementation is slow to
- * handle the incoming input.
+ * handle the incoming input. The events received by these callbacks are never null.
  */
 class InputConsumerCallbacks {
 public:
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 6548810..7d11f76 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -275,7 +275,7 @@
      * Return DEAD_OBJECT if the channel's peer has been closed.
      * Other errors probably indicate that the channel is broken.
      */
-    status_t receiveMessage(InputMessage* msg);
+    android::base::Result<InputMessage> receiveMessage();
 
     /* Tells whether there is a message in the channel available to be received.
      *
@@ -363,7 +363,8 @@
      * Returns OK on success.
      * Returns WOULD_BLOCK if the channel is full.
      * Returns DEAD_OBJECT if the channel's peer has been closed.
-     * Returns BAD_VALUE if seq is 0 or if pointerCount is less than 1 or greater than MAX_POINTERS.
+     * Returns BAD_VALUE if seq is 0 or if pointerCount is less than 1 or greater than MAX_POINTERS,
+     * or if the verifier is enabled and the event failed verification upon publishing.
      * Other errors probably indicate that the channel is broken.
      */
     status_t publishMotionEvent(uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source,
diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h
index 222dac8..9fbae73 100644
--- a/include/input/VirtualInputDevice.h
+++ b/include/input/VirtualInputDevice.h
@@ -77,6 +77,8 @@
 
 private:
     static const std::map<int, int> BUTTON_CODE_MAPPING;
+    int32_t mAccumulatedHighResScrollX;
+    int32_t mAccumulatedHighResScrollY;
 };
 
 class VirtualTouchscreen : public VirtualInputDevice {
@@ -122,4 +124,11 @@
     bool handleStylusUp(uint16_t tool, std::chrono::nanoseconds eventTime);
 };
 
+class VirtualRotaryEncoder : public VirtualInputDevice {
+public:
+    VirtualRotaryEncoder(android::base::unique_fd fd);
+    virtual ~VirtualRotaryEncoder() override;
+    bool writeScrollEvent(float scrollAmount, std::chrono::nanoseconds eventTime);
+};
+
 } // namespace android
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index ff6b558..2699368 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -62,7 +62,7 @@
 
     status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             InputWindowCommands commands, int64_t desiredPresentTime, bool isAutoTimestamp,
             const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
             const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index f5d19aa..83fc827 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -43,12 +43,6 @@
 
 } // Anonymous namespace
 
-namespace { // Anonymous
-
-constexpr int32_t kSerializedCallbackTypeOnCompelteWithJankData = 2;
-
-} // Anonymous namespace
-
 status_t FrameEventHistoryStats::writeToParcel(Parcel* output) const {
     status_t err = output->writeUint64(frameNumber);
     if (err != NO_ERROR) return err;
@@ -119,23 +113,6 @@
     return err;
 }
 
-JankData::JankData()
-      : frameVsyncId(FrameTimelineInfo::INVALID_VSYNC_ID), jankType(JankType::None) {}
-
-status_t JankData::writeToParcel(Parcel* output) const {
-    SAFE_PARCEL(output->writeInt64, frameVsyncId);
-    SAFE_PARCEL(output->writeInt32, jankType);
-    SAFE_PARCEL(output->writeInt64, frameIntervalNs);
-    return NO_ERROR;
-}
-
-status_t JankData::readFromParcel(const Parcel* input) {
-    SAFE_PARCEL(input->readInt64, &frameVsyncId);
-    SAFE_PARCEL(input->readInt32, &jankType);
-    SAFE_PARCEL(input->readInt64, &frameIntervalNs);
-    return NO_ERROR;
-}
-
 status_t SurfaceStats::writeToParcel(Parcel* output) const {
     SAFE_PARCEL(output->writeStrongBinder, surfaceControl);
     if (const auto* acquireFence = std::get_if<sp<Fence>>(&acquireTimeOrFence)) {
@@ -160,10 +137,6 @@
 
     SAFE_PARCEL(output->writeUint32, currentMaxAcquiredBufferCount);
     SAFE_PARCEL(output->writeParcelable, eventStats);
-    SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(jankData.size()));
-    for (const auto& data : jankData) {
-        SAFE_PARCEL(output->writeParcelable, data);
-    }
     SAFE_PARCEL(output->writeParcelable, previousReleaseCallbackId);
     return NO_ERROR;
 }
@@ -200,13 +173,6 @@
     SAFE_PARCEL(input->readUint32, &currentMaxAcquiredBufferCount);
     SAFE_PARCEL(input->readParcelable, &eventStats);
 
-    int32_t jankData_size = 0;
-    SAFE_PARCEL_READ_SIZE(input->readInt32, &jankData_size, input->dataSize());
-    for (int i = 0; i < jankData_size; i++) {
-        JankData data;
-        SAFE_PARCEL(input->readParcelable, &data);
-        jankData.push_back(data);
-    }
     SAFE_PARCEL(input->readParcelable, &previousReleaseCallbackId);
     return NO_ERROR;
 }
@@ -371,11 +337,7 @@
 
 status_t CallbackId::writeToParcel(Parcel* output) const {
     SAFE_PARCEL(output->writeInt64, id);
-    if (type == Type::ON_COMPLETE && includeJankData) {
-        SAFE_PARCEL(output->writeInt32, kSerializedCallbackTypeOnCompelteWithJankData);
-    } else {
-        SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(type));
-    }
+    SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(type));
     return NO_ERROR;
 }
 
@@ -383,13 +345,7 @@
     SAFE_PARCEL(input->readInt64, &id);
     int32_t typeAsInt;
     SAFE_PARCEL(input->readInt32, &typeAsInt);
-    if (typeAsInt == kSerializedCallbackTypeOnCompelteWithJankData) {
-        type = Type::ON_COMPLETE;
-        includeJankData = true;
-    } else {
-        type = static_cast<CallbackId::Type>(typeAsInt);
-        includeJankData = false;
-    }
+    type = static_cast<CallbackId::Type>(typeAsInt);
     return NO_ERROR;
 }
 
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 3745805..307ae39 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -177,6 +177,7 @@
     }
 
     SAFE_PARCEL(output.write, stretchEffect);
+    SAFE_PARCEL(output.writeParcelable, edgeExtensionParameters);
     SAFE_PARCEL(output.write, bufferCrop);
     SAFE_PARCEL(output.write, destinationFrame);
     SAFE_PARCEL(output.writeInt32, static_cast<uint32_t>(trustedOverlay));
@@ -306,6 +307,7 @@
     }
 
     SAFE_PARCEL(input.read, stretchEffect);
+    SAFE_PARCEL(input.readParcelable, &edgeExtensionParameters);
     SAFE_PARCEL(input.read, bufferCrop);
     SAFE_PARCEL(input.read, destinationFrame);
     uint32_t trustedOverlayInt;
@@ -682,6 +684,10 @@
         what |= eStretchChanged;
         stretchEffect = other.stretchEffect;
     }
+    if (other.what & eEdgeExtensionChanged) {
+        what |= eEdgeExtensionChanged;
+        edgeExtensionParameters = other.edgeExtensionParameters;
+    }
     if (other.what & eBufferCropChanged) {
         what |= eBufferCropChanged;
         bufferCrop = other.bufferCrop;
@@ -783,6 +789,7 @@
     CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
     CHECK_DIFF(diff, eTrustedOverlayChanged, other, trustedOverlay);
     CHECK_DIFF(diff, eStretchChanged, other, stretchEffect);
+    CHECK_DIFF(diff, eEdgeExtensionChanged, other, edgeExtensionParameters);
     CHECK_DIFF(diff, eBufferCropChanged, other, bufferCrop);
     CHECK_DIFF(diff, eDestinationFrameChanged, other, destinationFrame);
     if (other.what & eProducerDisconnect) diff |= eProducerDisconnect;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index af91bb3..88d3a7c 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -22,6 +22,7 @@
 
 #include <android/gui/BnWindowInfosReportedListener.h>
 #include <android/gui/DisplayState.h>
+#include <android/gui/EdgeExtensionParameters.h>
 #include <android/gui/ISurfaceComposerClient.h>
 #include <android/gui/IWindowInfosListener.h>
 #include <android/gui/TrustedPresentationThresholds.h>
@@ -86,7 +87,8 @@
     return (((int64_t)getpid()) << 32) | ++idCounter;
 }
 
-void emptyCallback(nsecs_t, const sp<Fence>&, const std::vector<SurfaceControlStats>&) {}
+constexpr int64_t INVALID_VSYNC = -1;
+
 } // namespace
 
 const std::string SurfaceComposerClient::kEmpty{};
@@ -207,9 +209,168 @@
     return DefaultComposerClient::getComposerClient();
 }
 
+// ---------------------------------------------------------------------------
+
 JankDataListener::~JankDataListener() {
 }
 
+status_t JankDataListener::flushJankData() {
+    if (mLayerId == -1) {
+        return INVALID_OPERATION;
+    }
+
+    binder::Status status = ComposerServiceAIDL::getComposerService()->flushJankData(mLayerId);
+    return statusTFromBinderStatus(status);
+}
+
+std::mutex JankDataListenerFanOut::sFanoutInstanceMutex;
+std::unordered_map<int32_t, sp<JankDataListenerFanOut>> JankDataListenerFanOut::sFanoutInstances;
+
+binder::Status JankDataListenerFanOut::onJankData(const std::vector<gui::JankData>& jankData) {
+    // Find the highest VSync ID.
+    int64_t lastVsync = jankData.empty()
+            ? 0
+            : std::max_element(jankData.begin(), jankData.end(),
+                               [](const gui::JankData& jd1, const gui::JankData& jd2) {
+                                   return jd1.frameVsyncId < jd2.frameVsyncId;
+                               })
+                      ->frameVsyncId;
+
+    // Fan out the jank data callback.
+    std::vector<wp<JankDataListener>> listenersToRemove;
+    for (auto listener : getActiveListeners()) {
+        if (!listener->onJankDataAvailable(jankData) ||
+            (listener->mRemoveAfter >= 0 && listener->mRemoveAfter <= lastVsync)) {
+            listenersToRemove.push_back(listener);
+        }
+    }
+
+    return removeListeners(listenersToRemove)
+            ? binder::Status::ok()
+            : binder::Status::fromExceptionCode(binder::Status::EX_NULL_POINTER);
+}
+
+status_t JankDataListenerFanOut::addListener(sp<SurfaceControl> sc, sp<JankDataListener> listener) {
+    sp<IBinder> layer = sc->getHandle();
+    if (layer == nullptr) {
+        return UNEXPECTED_NULL;
+    }
+    int32_t layerId = sc->getLayerId();
+
+    sFanoutInstanceMutex.lock();
+    auto it = sFanoutInstances.find(layerId);
+    bool registerNeeded = it == sFanoutInstances.end();
+    sp<JankDataListenerFanOut> fanout;
+    if (registerNeeded) {
+        fanout = sp<JankDataListenerFanOut>::make(layerId);
+        sFanoutInstances.insert({layerId, fanout});
+    } else {
+        fanout = it->second;
+    }
+
+    fanout->mMutex.lock();
+    fanout->mListeners.insert(listener);
+    fanout->mMutex.unlock();
+
+    sFanoutInstanceMutex.unlock();
+
+    if (registerNeeded) {
+        binder::Status status =
+                ComposerServiceAIDL::getComposerService()->addJankListener(layer, fanout);
+        return statusTFromBinderStatus(status);
+    }
+    return OK;
+}
+
+status_t JankDataListenerFanOut::removeListener(sp<JankDataListener> listener) {
+    int32_t layerId = listener->mLayerId;
+    if (layerId == -1) {
+        return INVALID_OPERATION;
+    }
+
+    int64_t removeAfter = INVALID_VSYNC;
+    sp<JankDataListenerFanOut> fanout;
+
+    sFanoutInstanceMutex.lock();
+    auto it = sFanoutInstances.find(layerId);
+    if (it != sFanoutInstances.end()) {
+        fanout = it->second;
+        removeAfter = fanout->updateAndGetRemovalVSync();
+    }
+
+    if (removeAfter != INVALID_VSYNC) {
+        // Remove this instance from the map, so that no new listeners are added
+        // while we're scheduled to be removed.
+        sFanoutInstances.erase(layerId);
+    }
+    sFanoutInstanceMutex.unlock();
+
+    if (removeAfter < 0) {
+        return OK;
+    }
+
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->removeJankListener(layerId, fanout,
+                                                                          removeAfter);
+    return statusTFromBinderStatus(status);
+}
+
+std::vector<sp<JankDataListener>> JankDataListenerFanOut::getActiveListeners() {
+    std::scoped_lock<std::mutex> lock(mMutex);
+
+    std::vector<sp<JankDataListener>> listeners;
+    for (auto it = mListeners.begin(); it != mListeners.end();) {
+        auto listener = it->promote();
+        if (!listener) {
+            it = mListeners.erase(it);
+        } else {
+            listeners.push_back(std::move(listener));
+            it++;
+        }
+    }
+    return listeners;
+}
+
+bool JankDataListenerFanOut::removeListeners(const std::vector<wp<JankDataListener>>& listeners) {
+    std::scoped_lock<std::mutex> fanoutLock(sFanoutInstanceMutex);
+    std::scoped_lock<std::mutex> listenersLock(mMutex);
+
+    for (auto listener : listeners) {
+        mListeners.erase(listener);
+    }
+
+    if (mListeners.empty()) {
+        sFanoutInstances.erase(mLayerId);
+        return false;
+    }
+    return true;
+}
+
+int64_t JankDataListenerFanOut::updateAndGetRemovalVSync() {
+    std::scoped_lock<std::mutex> lock(mMutex);
+    if (mRemoveAfter >= 0) {
+        // We've already been scheduled to be removed. Don't schedule again.
+        return INVALID_VSYNC;
+    }
+
+    int64_t removeAfter = 0;
+    for (auto it = mListeners.begin(); it != mListeners.end();) {
+        auto listener = it->promote();
+        if (!listener) {
+            it = mListeners.erase(it);
+        } else if (listener->mRemoveAfter < 0) {
+            // We have at least one listener that's still interested. Don't remove.
+            return INVALID_VSYNC;
+        } else {
+            removeAfter = std::max(removeAfter, listener->mRemoveAfter);
+            it++;
+        }
+    }
+
+    mRemoveAfter = removeAfter;
+    return removeAfter;
+}
+
 // ---------------------------------------------------------------------------
 
 // TransactionCompletedListener does not use ANDROID_SINGLETON_STATIC_INSTANCE because it needs
@@ -256,14 +417,6 @@
                 surfaceControls,
         CallbackId::Type callbackType) {
     std::lock_guard<std::mutex> lock(mMutex);
-    return addCallbackFunctionLocked(callbackFunction, surfaceControls, callbackType);
-}
-
-CallbackId TransactionCompletedListener::addCallbackFunctionLocked(
-        const TransactionCompletedCallback& callbackFunction,
-        const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
-                surfaceControls,
-        CallbackId::Type callbackType) {
     startListeningLocked();
 
     CallbackId callbackId(getNextIdLocked(), callbackType);
@@ -272,33 +425,11 @@
 
     for (const auto& surfaceControl : surfaceControls) {
         callbackSurfaceControls[surfaceControl->getHandle()] = surfaceControl;
-
-        if (callbackType == CallbackId::Type::ON_COMPLETE &&
-            mJankListeners.count(surfaceControl->getLayerId()) != 0) {
-            callbackId.includeJankData = true;
-        }
     }
 
     return callbackId;
 }
 
-void TransactionCompletedListener::addJankListener(const sp<JankDataListener>& listener,
-                                                   sp<SurfaceControl> surfaceControl) {
-    std::lock_guard<std::mutex> lock(mMutex);
-    mJankListeners.insert({surfaceControl->getLayerId(), listener});
-}
-
-void TransactionCompletedListener::removeJankListener(const sp<JankDataListener>& listener) {
-    std::lock_guard<std::mutex> lock(mMutex);
-    for (auto it = mJankListeners.begin(); it != mJankListeners.end();) {
-        if (it->second == listener) {
-            it = mJankListeners.erase(it);
-        } else {
-            it++;
-        }
-    }
-}
-
 void TransactionCompletedListener::setReleaseBufferCallback(const ReleaseCallbackId& callbackId,
                                                             ReleaseBufferCallback listener) {
     std::scoped_lock<std::mutex> lock(mMutex);
@@ -325,32 +456,20 @@
 }
 
 void TransactionCompletedListener::addSurfaceControlToCallbacks(
-        SurfaceComposerClient::CallbackInfo& callbackInfo,
-        const sp<SurfaceControl>& surfaceControl) {
+        const sp<SurfaceControl>& surfaceControl,
+        const std::unordered_set<CallbackId, CallbackIdHash>& callbackIds) {
     std::lock_guard<std::mutex> lock(mMutex);
 
-    bool includingJankData = false;
-    for (auto callbackId : callbackInfo.callbackIds) {
+    for (auto callbackId : callbackIds) {
         mCallbacks[callbackId].surfaceControls.emplace(std::piecewise_construct,
                                                        std::forward_as_tuple(
                                                                surfaceControl->getHandle()),
                                                        std::forward_as_tuple(surfaceControl));
-        includingJankData = includingJankData || callbackId.includeJankData;
-    }
-
-    // If no registered callback is requesting jank data, but there is a jank listener registered
-    // on the new surface control, add a synthetic callback that requests the jank data.
-    if (!includingJankData && mJankListeners.count(surfaceControl->getLayerId()) != 0) {
-        CallbackId callbackId =
-                addCallbackFunctionLocked(&emptyCallback, callbackInfo.surfaceControls,
-                                          CallbackId::Type::ON_COMPLETE);
-        callbackInfo.callbackIds.emplace(callbackId);
     }
 }
 
 void TransactionCompletedListener::onTransactionCompleted(ListenerStats listenerStats) {
     std::unordered_map<CallbackId, CallbackTranslation, CallbackIdHash> callbacksMap;
-    std::multimap<int32_t, sp<JankDataListener>> jankListenersMap;
     {
         std::lock_guard<std::mutex> lock(mMutex);
 
@@ -366,7 +485,6 @@
          * sp<SurfaceControl> that could possibly exist for the callbacks.
          */
         callbacksMap = mCallbacks;
-        jankListenersMap = mJankListeners;
         for (const auto& transactionStats : listenerStats.transactionStats) {
             for (auto& callbackId : transactionStats.callbackIds) {
                 mCallbacks.erase(callbackId);
@@ -486,12 +604,6 @@
                         transactionStats.presentFence, surfaceStats);
                 }
             }
-
-            if (surfaceStats.jankData.empty()) continue;
-            auto jankRange = jankListenersMap.equal_range(layerId);
-            for (auto it = jankRange.first; it != jankRange.second; it++) {
-                it->second->onJankDataAvailable(surfaceStats.jankData);
-            }
         }
     }
 }
@@ -1004,8 +1116,9 @@
 
         // register all surface controls for all callbackIds for this listener that is merging
         for (const auto& surfaceControl : currentProcessCallbackInfo.surfaceControls) {
-            mTransactionCompletedListener->addSurfaceControlToCallbacks(currentProcessCallbackInfo,
-                                                                        surfaceControl);
+            mTransactionCompletedListener
+                    ->addSurfaceControlToCallbacks(surfaceControl,
+                                                   currentProcessCallbackInfo.callbackIds);
         }
     }
 
@@ -1059,7 +1172,8 @@
     uncacheBuffer.token = BufferCache::getInstance().getToken();
     uncacheBuffer.id = cacheId;
     Vector<ComposerState> composerStates;
-    status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
+    Vector<DisplayState> displayStates;
+    status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, displayStates,
                                               ISurfaceComposer::eOneWay,
                                               Transaction::getDefaultApplyToken(), {}, systemTime(),
                                               true, {uncacheBuffer}, false, {}, generateId(), {});
@@ -1361,7 +1475,7 @@
     auto& callbackInfo = mListenerCallbacks[TransactionCompletedListener::getIInstance()];
     callbackInfo.surfaceControls.insert(sc);
 
-    mTransactionCompletedListener->addSurfaceControlToCallbacks(callbackInfo, sc);
+    mTransactionCompletedListener->addSurfaceControlToCallbacks(sc, callbackInfo.callbackIds);
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setPosition(
@@ -2217,6 +2331,19 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setEdgeExtensionEffect(
+        const sp<SurfaceControl>& sc, const gui::EdgeExtensionParameters& effect) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+
+    s->what |= layer_state_t::eEdgeExtensionChanged;
+    s->edgeExtensionParameters = effect;
+    return *this;
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBufferCrop(
         const sp<SurfaceControl>& sc, const Rect& bufferCrop) {
     layer_state_t* s = getLayerState(sc);
diff --git a/libs/gui/aidl/android/gui/EdgeExtensionParameters.aidl b/libs/gui/aidl/android/gui/EdgeExtensionParameters.aidl
new file mode 100644
index 0000000..44f4259
--- /dev/null
+++ b/libs/gui/aidl/android/gui/EdgeExtensionParameters.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+
+/** @hide */
+parcelable EdgeExtensionParameters {
+    // These represent the translation of the window as requested by the animation
+    boolean extendRight;
+    boolean extendLeft;
+    boolean extendTop;
+    boolean extendBottom;
+}
\ No newline at end of file
diff --git a/libs/gui/aidl/android/gui/IJankListener.aidl b/libs/gui/aidl/android/gui/IJankListener.aidl
new file mode 100644
index 0000000..2bfd1af
--- /dev/null
+++ b/libs/gui/aidl/android/gui/IJankListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+import android.gui.JankData;
+
+/** @hide */
+interface IJankListener {
+
+  /**
+   * Callback reporting jank data of the most recent frames.
+   * @See {@link ISurfaceComposer#addJankListener(IBinder, IJankListener)}
+   */
+  void onJankData(in JankData[] data);
+}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index 6d018ea..ac14138 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -42,6 +42,7 @@
 import android.gui.ITunnelModeEnabledListener;
 import android.gui.IWindowInfosListener;
 import android.gui.IWindowInfosPublisher;
+import android.gui.IJankListener;
 import android.gui.LayerCaptureArgs;
 import android.gui.OverlayProperties;
 import android.gui.PullAtomData;
@@ -580,4 +581,22 @@
      * This method should not block the ShutdownThread therefore it's handled asynchronously.
      */
     oneway void notifyShutdown();
+
+    /**
+     * Registers the jank listener on the given layer to receive jank data of future frames.
+     */
+    void addJankListener(IBinder layer, IJankListener listener);
+
+    /**
+     * Flushes any pending jank data on the given layer to any registered listeners on that layer.
+     */
+    oneway void flushJankData(int layerId);
+
+    /**
+     * Schedules the removal of the jank listener from the given layer after the VSync with the
+     * specified ID. Use a value <= 0 for afterVsync to remove the listener immediately. The given
+     * listener will not be removed before the given VSync, but may still receive data for frames
+     * past the provided VSync.
+     */
+    oneway void removeJankListener(int layerId, IJankListener listener, long afterVsync);
 }
diff --git a/libs/gui/aidl/android/gui/JankData.aidl b/libs/gui/aidl/android/gui/JankData.aidl
new file mode 100644
index 0000000..7ea9d22
--- /dev/null
+++ b/libs/gui/aidl/android/gui/JankData.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.gui;
+
+ /** @hide */
+parcelable JankData {
+  /**
+   * Identifier for the frame submitted with Transaction.setFrameTimelineVsyncId
+   */
+  long frameVsyncId;
+
+  /**
+   * Bitmask of jank types that occurred.
+   */
+  int jankType;
+
+  /**
+   * Expected duration in nanoseconds of this frame.
+   */
+  long frameIntervalNs;
+}
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index eb4a802..1ecc216 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -112,7 +112,7 @@
     /* open/close transactions. requires ACCESS_SURFACE_FLINGER permission */
     virtual status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffer,
             bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
index bc97cd0..014029b 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -16,8 +16,6 @@
 
 #pragma once
 
-#include "JankInfo.h"
-
 #include <binder/IInterface.h>
 #include <binder/Parcel.h>
 #include <binder/Parcelable.h>
@@ -40,15 +38,10 @@
 class CallbackId : public Parcelable {
 public:
     int64_t id;
-    enum class Type : int32_t {
-        ON_COMPLETE = 0,
-        ON_COMMIT = 1,
-        /*reserved for serialization = 2*/
-    } type;
-    bool includeJankData; // Only respected for ON_COMPLETE callbacks.
+    enum class Type : int32_t { ON_COMPLETE = 0, ON_COMMIT = 1 } type;
 
     CallbackId() {}
-    CallbackId(int64_t id, Type type) : id(id), type(type), includeJankData(false) {}
+    CallbackId(int64_t id, Type type) : id(id), type(type) {}
     status_t writeToParcel(Parcel* output) const override;
     status_t readFromParcel(const Parcel* input) override;
 
@@ -113,29 +106,6 @@
     nsecs_t dequeueReadyTime;
 };
 
-/**
- * Jank information representing SurfaceFlinger's jank classification about frames for a specific
- * surface.
- */
-class JankData : public Parcelable {
-public:
-    status_t writeToParcel(Parcel* output) const override;
-    status_t readFromParcel(const Parcel* input) override;
-
-    JankData();
-    JankData(int64_t frameVsyncId, int32_t jankType, nsecs_t frameIntervalNs)
-          : frameVsyncId(frameVsyncId), jankType(jankType), frameIntervalNs(frameIntervalNs) {}
-
-    // Identifier for the frame submitted with Transaction.setFrameTimelineVsyncId
-    int64_t frameVsyncId;
-
-    // Bitmask of janks that occurred
-    int32_t jankType;
-
-    // Expected duration of the frame
-    nsecs_t frameIntervalNs;
-};
-
 class SurfaceStats : public Parcelable {
 public:
     status_t writeToParcel(Parcel* output) const override;
@@ -145,14 +115,13 @@
     SurfaceStats(const sp<IBinder>& sc, std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence,
                  const sp<Fence>& prevReleaseFence, std::optional<uint32_t> hint,
                  uint32_t currentMaxAcquiredBuffersCount, FrameEventHistoryStats frameEventStats,
-                 std::vector<JankData> jankData, ReleaseCallbackId previousReleaseCallbackId)
+                 ReleaseCallbackId previousReleaseCallbackId)
           : surfaceControl(sc),
             acquireTimeOrFence(std::move(acquireTimeOrFence)),
             previousReleaseFence(prevReleaseFence),
             transformHint(hint),
             currentMaxAcquiredBufferCount(currentMaxAcquiredBuffersCount),
             eventStats(frameEventStats),
-            jankData(std::move(jankData)),
             previousReleaseCallbackId(previousReleaseCallbackId) {}
 
     sp<IBinder> surfaceControl;
@@ -161,7 +130,6 @@
     std::optional<uint32_t> transformHint = 0;
     uint32_t currentMaxAcquiredBufferCount = 0;
     FrameEventHistoryStats eventStats;
-    std::vector<JankData> jankData;
     ReleaseCallbackId previousReleaseCallbackId;
 };
 
diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h
index 9cf62bc..7ee291d 100644
--- a/libs/gui/include/gui/LayerMetadata.h
+++ b/libs/gui/include/gui/LayerMetadata.h
@@ -74,6 +74,7 @@
 } // namespace android::gui
 
 using android::gui::METADATA_ACCESSIBILITY_ID;
+using android::gui::METADATA_CALLING_UID;
 using android::gui::METADATA_DEQUEUE_TIME;
 using android::gui::METADATA_GAME_MODE;
 using android::gui::METADATA_MOUSE_CURSOR;
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 5f2f8dc..3fb1894 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -29,6 +29,7 @@
 #include <math/mat4.h>
 
 #include <android/gui/DropInputMode.h>
+#include <android/gui/EdgeExtensionParameters.h>
 #include <android/gui/FocusRequest.h>
 #include <android/gui/TrustedOverlay.h>
 
@@ -218,6 +219,7 @@
         eTrustedOverlayChanged = 0x4000'00000000,
         eDropInputModeChanged = 0x8000'00000000,
         eExtendedRangeBrightnessChanged = 0x10000'00000000,
+        eEdgeExtensionChanged = 0x20000'00000000,
     };
 
     layer_state_t();
@@ -241,7 +243,7 @@
             layer_state_t::eCropChanged | layer_state_t::eDestinationFrameChanged |
             layer_state_t::eMatrixChanged | layer_state_t::ePositionChanged |
             layer_state_t::eTransformToDisplayInverseChanged |
-            layer_state_t::eTransparentRegionChanged;
+            layer_state_t::eTransparentRegionChanged | layer_state_t::eEdgeExtensionChanged;
 
     // Buffer and related updates.
     static constexpr uint64_t BUFFER_CHANGES = layer_state_t::eApiChanged |
@@ -393,6 +395,9 @@
     // Stretch effect to be applied to this layer
     StretchEffect stretchEffect;
 
+    // Edge extension effect to be applied to this layer
+    gui::EdgeExtensionParameters edgeExtensionParameters;
+
     Rect bufferCrop;
     Rect destinationFrame;
 
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 0862e03..a10283b 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -35,12 +35,14 @@
 #include <ui/BlurRegion.h>
 #include <ui/ConfigStoreTypes.h>
 #include <ui/DisplayedFrameStats.h>
+#include <ui/EdgeExtensionEffect.h>
 #include <ui/FrameStats.h>
 #include <ui/GraphicTypes.h>
 #include <ui/PixelFormat.h>
 #include <ui/Rotation.h>
 #include <ui/StaticDisplayInfo.h>
 
+#include <android/gui/BnJankListener.h>
 #include <android/gui/ISurfaceComposerClient.h>
 
 #include <gui/CpuConsumer.h>
@@ -743,6 +745,17 @@
         Transaction& setStretchEffect(const sp<SurfaceControl>& sc,
                                       const StretchEffect& stretchEffect);
 
+        /**
+         * Provides the edge extension effect configured on a container that the
+         * surface is rendered within.
+         * @param sc target surface the edge extension should be applied to
+         * @param effect the corresponding EdgeExtensionParameters to be applied
+         *    to the surface.
+         * @return The transaction being constructed
+         */
+        Transaction& setEdgeExtensionEffect(const sp<SurfaceControl>& sc,
+                                            const gui::EdgeExtensionParameters& effect);
+
         Transaction& setBufferCrop(const sp<SurfaceControl>& sc, const Rect& bufferCrop);
         Transaction& setDestinationFrame(const sp<SurfaceControl>& sc,
                                          const Rect& destinationFrame);
@@ -864,12 +877,82 @@
 
 // ---------------------------------------------------------------------------
 
-class JankDataListener : public VirtualLightRefBase {
+class JankDataListener;
+
+// Acts as a representative listener to the composer for a single layer and
+// forwards any received jank data to multiple listeners. Will remove itself
+// from the composer only once the last listener is removed.
+class JankDataListenerFanOut : public gui::BnJankListener {
 public:
-    virtual ~JankDataListener() = 0;
-    virtual void onJankDataAvailable(const std::vector<JankData>& jankData) = 0;
+    JankDataListenerFanOut(int32_t layerId) : mLayerId(layerId) {}
+
+    binder::Status onJankData(const std::vector<gui::JankData>& jankData) override;
+
+    static status_t addListener(sp<SurfaceControl> sc, sp<JankDataListener> listener);
+    static status_t removeListener(sp<JankDataListener> listener);
+
+private:
+    std::vector<sp<JankDataListener>> getActiveListeners();
+    bool removeListeners(const std::vector<wp<JankDataListener>>& listeners);
+    int64_t updateAndGetRemovalVSync();
+
+    struct WpJDLHash {
+        std::size_t operator()(const wp<JankDataListener>& listener) const {
+            return std::hash<JankDataListener*>{}(listener.unsafe_get());
+        }
+    };
+
+    std::mutex mMutex;
+    std::unordered_set<wp<JankDataListener>, WpJDLHash> mListeners GUARDED_BY(mMutex);
+    int32_t mLayerId;
+    int64_t mRemoveAfter = -1;
+
+    static std::mutex sFanoutInstanceMutex;
+    static std::unordered_map<int32_t, sp<JankDataListenerFanOut>> sFanoutInstances;
 };
 
+// Base class for client listeners interested in jank classification data from
+// the composer. Subclasses should override onJankDataAvailable and call the add
+// and removal methods to receive jank data.
+class JankDataListener : public virtual RefBase {
+public:
+    JankDataListener() {}
+    virtual ~JankDataListener();
+
+    virtual bool onJankDataAvailable(const std::vector<gui::JankData>& jankData) = 0;
+
+    status_t addListener(sp<SurfaceControl> sc) {
+        if (mLayerId != -1) {
+            removeListener(0);
+            mLayerId = -1;
+        }
+
+        int32_t layerId = sc->getLayerId();
+        status_t status =
+                JankDataListenerFanOut::addListener(std::move(sc),
+                                                    sp<JankDataListener>::fromExisting(this));
+        if (status == OK) {
+            mLayerId = layerId;
+        }
+        return status;
+    }
+
+    status_t removeListener(int64_t afterVsync) {
+        mRemoveAfter = std::max(static_cast<int64_t>(0), afterVsync);
+        return JankDataListenerFanOut::removeListener(sp<JankDataListener>::fromExisting(this));
+    }
+
+    status_t flushJankData();
+
+    friend class JankDataListenerFanOut;
+
+private:
+    int32_t mLayerId = -1;
+    int64_t mRemoveAfter = -1;
+};
+
+// ---------------------------------------------------------------------------
+
 class TransactionCompletedListener : public BnTransactionCompletedListener {
 public:
     TransactionCompletedListener();
@@ -904,7 +987,6 @@
 
     std::unordered_map<CallbackId, CallbackTranslation, CallbackIdHash> mCallbacks
             GUARDED_BY(mMutex);
-    std::multimap<int32_t, sp<JankDataListener>> mJankListeners GUARDED_BY(mMutex);
     std::unordered_map<ReleaseCallbackId, ReleaseBufferCallback, ReleaseBufferCallbackIdHash>
             mReleaseBufferCallbacks GUARDED_BY(mMutex);
 
@@ -927,14 +1009,10 @@
             const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
                     surfaceControls,
             CallbackId::Type callbackType);
-    CallbackId addCallbackFunctionLocked(
-            const TransactionCompletedCallback& callbackFunction,
-            const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
-                    surfaceControls,
-            CallbackId::Type callbackType) REQUIRES(mMutex);
 
-    void addSurfaceControlToCallbacks(SurfaceComposerClient::CallbackInfo& callbackInfo,
-                                      const sp<SurfaceControl>& surfaceControl);
+    void addSurfaceControlToCallbacks(
+            const sp<SurfaceControl>& surfaceControl,
+            const std::unordered_set<CallbackId, CallbackIdHash>& callbackIds);
 
     void addQueueStallListener(std::function<void(const std::string&)> stallListener, void* id);
     void removeQueueStallListener(void *id);
@@ -943,18 +1021,6 @@
             TrustedPresentationCallback tpc, int id, void* context);
     void clearTrustedPresentationCallback(int id);
 
-    /*
-     * Adds a jank listener to be informed about SurfaceFlinger's jank classification for a specific
-     * surface. Jank classifications arrive as part of the transaction callbacks about previous
-     * frames submitted to this Surface.
-     */
-    void addJankListener(const sp<JankDataListener>& listener, sp<SurfaceControl> surfaceControl);
-
-    /**
-     * Removes a jank listener previously added to addJankCallback.
-     */
-    void removeJankListener(const sp<JankDataListener>& listener);
-
     void addSurfaceStatsListener(void* context, void* cookie, sp<SurfaceControl> surfaceControl,
                 SurfaceStatsCallback listener);
     void removeSurfaceStatsListener(void* context, void* cookie);
diff --git a/libs/gui/tests/RegionSampling_test.cpp b/libs/gui/tests/RegionSampling_test.cpp
index b18b544..223e4b6 100644
--- a/libs/gui/tests/RegionSampling_test.cpp
+++ b/libs/gui/tests/RegionSampling_test.cpp
@@ -239,8 +239,9 @@
     float const luma_green = 0.7152;
     uint32_t const rgba_blue = 0xFFFF0000;
     float const luma_blue = 0.0722;
-    float const error_margin = 0.01;
+    float const error_margin = 0.1;
     float const luma_gray = 0.50;
+    static constexpr std::chrono::milliseconds EVENT_WAIT_TIME_MS = 5000ms;
 };
 
 TEST_F(RegionSamplingTest, invalidLayerHandle_doesNotCrash) {
@@ -261,7 +262,7 @@
     composer->removeRegionSamplingListener(listener);
 }
 
-TEST_F(RegionSamplingTest, DISABLED_CollectsLuma) {
+TEST_F(RegionSamplingTest, CollectsLuma) {
     fill_render(rgba_green);
 
     sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
@@ -273,7 +274,30 @@
     sampleArea.bottom = 200;
     composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
 
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
+    EXPECT_NEAR(listener->luma(), luma_green, error_margin);
+
+    composer->removeRegionSamplingListener(listener);
+}
+
+TEST_F(RegionSamplingTest, CollectsLumaForSecureLayer) {
+    fill_render(rgba_green);
+    SurfaceComposerClient::Transaction()
+            .setFlags(mContentLayer, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure)
+            .apply(/*synchronous=*/true);
+
+    sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
+    sp<Listener> listener = new Listener();
+    gui::ARect sampleArea;
+    sampleArea.left = 100;
+    sampleArea.top = 100;
+    sampleArea.right = 200;
+    sampleArea.bottom = 200;
+    composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
+
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_green, error_margin);
 
     composer->removeRegionSamplingListener(listener);
@@ -291,13 +315,14 @@
     sampleArea.bottom = 200;
     composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
 
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_green, error_margin);
 
     listener->reset();
 
     fill_render(rgba_blue);
-    EXPECT_TRUE(listener->wait_event(300ms))
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
             << "timed out waiting for 2nd luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_blue, error_margin);
 
@@ -323,10 +348,10 @@
     graySampleArea.bottom = 200;
     composer->addRegionSamplingListener(graySampleArea, mTopLayer->getHandle(), grayListener);
 
-    EXPECT_TRUE(grayListener->wait_event(300ms))
+    EXPECT_TRUE(grayListener->wait_event(EVENT_WAIT_TIME_MS))
             << "timed out waiting for luma event to be received";
     EXPECT_NEAR(grayListener->luma(), luma_gray, error_margin);
-    EXPECT_TRUE(greenListener->wait_event(300ms))
+    EXPECT_TRUE(greenListener->wait_event(EVENT_WAIT_TIME_MS))
             << "timed out waiting for luma event to be received";
     EXPECT_NEAR(greenListener->luma(), luma_green, error_margin);
 
@@ -334,7 +359,7 @@
     composer->removeRegionSamplingListener(grayListener);
 }
 
-TEST_F(RegionSamplingTest, DISABLED_TestIfInvalidInputParameters) {
+TEST_F(RegionSamplingTest, TestIfInvalidInputParameters) {
     sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
     sp<Listener> listener = new Listener();
 
@@ -369,7 +394,7 @@
     composer->removeRegionSamplingListener(listener);
 }
 
-TEST_F(RegionSamplingTest, DISABLED_TestCallbackAfterRemoveListener) {
+TEST_F(RegionSamplingTest, TestCallbackAfterRemoveListener) {
     fill_render(rgba_green);
     sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
     sp<Listener> listener = new Listener();
@@ -381,7 +406,8 @@
     composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
     fill_render(rgba_green);
 
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_green, error_margin);
 
     listener->reset();
@@ -404,11 +430,13 @@
     // Test: listener in (100, 100). See layer before move, no layer after move.
     fill_render(rgba_blue);
     composer->addRegionSamplingListener(sampleAreaA, mTopLayer->getHandle(), listener);
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_blue, error_margin);
     listener->reset();
     SurfaceComposerClient::Transaction{}.setPosition(mContentLayer, 600, 600).apply();
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_gray, error_margin);
     composer->removeRegionSamplingListener(listener);
 
@@ -420,11 +448,13 @@
     sampleAreaA.right = sampleArea.right;
     sampleAreaA.bottom = sampleArea.bottom;
     composer->addRegionSamplingListener(sampleAreaA, mTopLayer->getHandle(), listener);
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_gray, error_margin);
     listener->reset();
     SurfaceComposerClient::Transaction{}.setPosition(mContentLayer, 600, 600).apply();
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_green, error_margin);
     composer->removeRegionSamplingListener(listener);
 }
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 43cd0f8..1d3174c 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -636,7 +636,7 @@
 
     status_t setTransactionState(
             const FrameTimelineInfo& /*frameTimelineInfo*/, Vector<ComposerState>& /*state*/,
-            const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
+            Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
             const sp<IBinder>& /*applyToken*/, InputWindowCommands /*inputWindowCommands*/,
             int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
             const std::vector<client_cache_t>& /*cachedBuffer*/, bool /*hasListenerCallbacks*/,
@@ -987,6 +987,19 @@
 
     binder::Status notifyShutdown() override { return binder::Status::ok(); }
 
+    binder::Status addJankListener(const sp<IBinder>& /*layer*/,
+                                   const sp<gui::IJankListener>& /*listener*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status flushJankData(int32_t /*layerId*/) override { return binder::Status::ok(); }
+
+    binder::Status removeJankListener(int32_t /*layerId*/,
+                                      const sp<gui::IJankListener>& /*listener*/,
+                                      int64_t /*afterVsync*/) override {
+        return binder::Status::ok();
+    }
+
 protected:
     IBinder* onAsBinder() override { return nullptr; }
 
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index d782f42..45ebc66 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -30,6 +30,7 @@
         "android/os/InputEventInjectionResult.aidl",
         "android/os/InputEventInjectionSync.aidl",
         "android/os/InputConfig.aidl",
+        "android/os/MotionEventFlag.aidl",
         "android/os/PointerIconType.aidl",
     ],
 }
@@ -257,6 +258,7 @@
     ],
 
     shared_libs: [
+        "android.companion.virtualdevice.flags-aconfig-cc-host",
         "libbase",
         "libbinder",
         "libbinder_ndk",
diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp
index fcf490d..dce528f 100644
--- a/libs/input/InputConsumer.cpp
+++ b/libs/input/InputConsumer.cpp
@@ -235,8 +235,9 @@
             mMsgDeferred = false;
         } else {
             // Receive a fresh message.
-            status_t result = mChannel->receiveMessage(&mMsg);
-            if (result == OK) {
+            android::base::Result<InputMessage> result = mChannel->receiveMessage();
+            if (result.ok()) {
+                mMsg = std::move(result.value());
                 const auto [_, inserted] =
                         mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
                 LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
@@ -244,11 +245,11 @@
 
                 // Trace the event processing timeline - event was just read from the socket
                 ATRACE_ASYNC_BEGIN(mProcessingTraceTag.c_str(), /*cookie=*/mMsg.header.seq);
-            }
-            if (result) {
+            } else {
                 // Consume the next batched event unless batches are being held for later.
-                if (consumeBatches || result != WOULD_BLOCK) {
-                    result = consumeBatch(factory, frameTime, outSeq, outEvent);
+                if (consumeBatches || result.error().code() != WOULD_BLOCK) {
+                    result = android::base::Error(
+                            consumeBatch(factory, frameTime, outSeq, outEvent));
                     if (*outEvent) {
                         ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
                                  "channel '%s' consumer ~ consumed batch event, seq=%u",
@@ -256,7 +257,7 @@
                         break;
                     }
                 }
-                return result;
+                return result.error().code();
             }
         }
 
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index 15d992f..e193983 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -362,36 +362,36 @@
 std::vector<InputMessage> InputConsumerNoResampling::readAllMessages() {
     std::vector<InputMessage> messages;
     while (true) {
-        InputMessage msg;
-        status_t result = mChannel->receiveMessage(&msg);
-        switch (result) {
-            case OK: {
-                const auto [_, inserted] =
-                        mConsumeTimes.emplace(msg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
-                LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
-                                    msg.header.seq);
+        android::base::Result<InputMessage> result = mChannel->receiveMessage();
+        if (result.ok()) {
+            const InputMessage& msg = *result;
+            const auto [_, inserted] =
+                    mConsumeTimes.emplace(msg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
+            LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
+                                msg.header.seq);
 
-                // Trace the event processing timeline - event was just read from the socket
-                // TODO(b/329777420): distinguish between multiple instances of InputConsumer
-                // in the same process.
-                ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/msg.header.seq);
-                messages.push_back(msg);
-                break;
-            }
-            case WOULD_BLOCK: {
-                return messages;
-            }
-            case DEAD_OBJECT: {
-                LOG(FATAL) << "Got a dead object for " << mChannel->getName();
-                break;
-            }
-            case BAD_VALUE: {
-                LOG(FATAL) << "Got a bad value for " << mChannel->getName();
-                break;
-            }
-            default: {
-                LOG(FATAL) << "Unexpected error: " << result;
-                break;
+            // Trace the event processing timeline - event was just read from the socket
+            // TODO(b/329777420): distinguish between multiple instances of InputConsumer
+            // in the same process.
+            ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/msg.header.seq);
+            messages.push_back(msg);
+        } else { // !result.ok()
+            switch (result.error().code()) {
+                case WOULD_BLOCK: {
+                    return messages;
+                }
+                case DEAD_OBJECT: {
+                    LOG(FATAL) << "Got a dead object for " << mChannel->getName();
+                    break;
+                }
+                case BAD_VALUE: {
+                    LOG(FATAL) << "Got a bad value for " << mChannel->getName();
+                    break;
+                }
+                default: {
+                    LOG(FATAL) << "Unexpected error: " << result.error().message();
+                    break;
+                }
             }
         }
     }
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 47b4228..77dcaa9 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -375,13 +375,11 @@
 
     sp<IBinder> token = sp<BBinder>::make();
 
-    std::string serverChannelName = name + " (server)";
     android::base::unique_fd serverFd(sockets[0]);
-    outServerChannel = InputChannel::create(serverChannelName, std::move(serverFd), token);
+    outServerChannel = InputChannel::create(name, std::move(serverFd), token);
 
-    std::string clientChannelName = name + " (client)";
     android::base::unique_fd clientFd(sockets[1]);
-    outClientChannel = InputChannel::create(clientChannelName, std::move(clientFd), token);
+    outClientChannel = InputChannel::create(name, std::move(clientFd), token);
     return OK;
 }
 
@@ -424,10 +422,11 @@
     return OK;
 }
 
-status_t InputChannel::receiveMessage(InputMessage* msg) {
+android::base::Result<InputMessage> InputChannel::receiveMessage() {
     ssize_t nRead;
+    InputMessage msg;
     do {
-        nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT);
+        nRead = ::recv(getFd(), &msg, sizeof(InputMessage), MSG_DONTWAIT);
     } while (nRead == -1 && errno == EINTR);
 
     if (nRead < 0) {
@@ -435,36 +434,36 @@
         ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ receive message failed, errno=%d",
                  name.c_str(), errno);
         if (error == EAGAIN || error == EWOULDBLOCK) {
-            return WOULD_BLOCK;
+            return android::base::Error(WOULD_BLOCK);
         }
         if (error == EPIPE || error == ENOTCONN || error == ECONNREFUSED) {
-            return DEAD_OBJECT;
+            return android::base::Error(DEAD_OBJECT);
         }
-        return -error;
+        return android::base::Error(-error);
     }
 
     if (nRead == 0) { // check for EOF
         ALOGD_IF(DEBUG_CHANNEL_MESSAGES,
                  "channel '%s' ~ receive message failed because peer was closed", name.c_str());
-        return DEAD_OBJECT;
+        return android::base::Error(DEAD_OBJECT);
     }
 
-    if (!msg->isValid(nRead)) {
+    if (!msg.isValid(nRead)) {
         ALOGE("channel '%s' ~ received invalid message of size %zd", name.c_str(), nRead);
-        return BAD_VALUE;
+        return android::base::Error(BAD_VALUE);
     }
 
     ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", name.c_str(),
-             ftl::enum_string(msg->header.type).c_str());
+             ftl::enum_string(msg.header.type).c_str());
     if (ATRACE_ENABLED()) {
         // Add an additional trace point to include data about the received message.
         std::string message =
                 StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=%s)",
-                             name.c_str(), msg->header.seq,
-                             ftl::enum_string(msg->header.type).c_str());
+                             name.c_str(), msg.header.seq,
+                             ftl::enum_string(msg.header.type).c_str());
         ATRACE_NAME(message.c_str());
     }
-    return OK;
+    return msg;
 }
 
 bool InputChannel::probablyHasInput() const {
@@ -589,7 +588,8 @@
                 mInputVerifier.processMovement(deviceId, source, action, pointerCount,
                                                pointerProperties, pointerCoords, flags);
         if (!result.ok()) {
-            LOG(FATAL) << "Bad stream: " << result.error();
+            LOG(ERROR) << "Bad stream: " << result.error();
+            return BAD_VALUE;
         }
     }
     if (debugTransportPublisher()) {
@@ -729,15 +729,16 @@
 }
 
 android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveConsumerResponse() {
-    InputMessage msg;
-    status_t result = mChannel->receiveMessage(&msg);
-    if (result) {
-        if (debugTransportPublisher() && result != WOULD_BLOCK) {
+    android::base::Result<InputMessage> result = mChannel->receiveMessage();
+    if (!result.ok()) {
+        if (debugTransportPublisher() && result.error().code() != WOULD_BLOCK) {
             LOG(INFO) << "channel '" << mChannel->getName() << "' publisher ~ " << __func__ << ": "
-                      << strerror(result);
+                      << result.error().message();
         }
-        return android::base::Error(result);
+        return result.error();
     }
+
+    const InputMessage& msg = *result;
     if (msg.header.type == InputMessage::Type::FINISHED) {
         ALOGD_IF(debugTransportPublisher(),
                  "channel '%s' publisher ~ %s: finished: seq=%u, handled=%s",
diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
index eea06f1..2e3e1a0 100644
--- a/libs/input/VirtualInputDevice.cpp
+++ b/libs/input/VirtualInputDevice.cpp
@@ -18,6 +18,7 @@
 
 #include <android/input.h>
 #include <android/keycodes.h>
+#include <android_companion_virtualdevice_flags.h>
 #include <fcntl.h>
 #include <input/Input.h>
 #include <input/VirtualInputDevice.h>
@@ -40,6 +41,8 @@
 
 namespace android {
 
+namespace vd_flags = android::companion::virtualdevice::flags;
+
 VirtualInputDevice::VirtualInputDevice(unique_fd fd) : mFd(std::move(fd)) {}
 
 VirtualInputDevice::~VirtualInputDevice() {
@@ -253,7 +256,10 @@
         // clang-format on
 };
 
-VirtualMouse::VirtualMouse(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
+VirtualMouse::VirtualMouse(unique_fd fd)
+      : VirtualInputDevice(std::move(fd)),
+        mAccumulatedHighResScrollX(0),
+        mAccumulatedHighResScrollY(0) {}
 
 VirtualMouse::~VirtualMouse() {}
 
@@ -272,9 +278,43 @@
 
 bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement,
                                     std::chrono::nanoseconds eventTime) {
-    return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement, eventTime) &&
-            writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement, eventTime) &&
-            writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime);
+    if (!vd_flags::high_resolution_scroll()) {
+        return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement, eventTime) &&
+                writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement, eventTime) &&
+                writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime);
+    }
+
+    const int32_t highResScrollX = xAxisMovement * kEvdevMouseHighResScrollUnitsPerDetent;
+    const int32_t highResScrollY = yAxisMovement * kEvdevMouseHighResScrollUnitsPerDetent;
+    bool highResScrollResult =
+            writeInputEvent(EV_REL, REL_HWHEEL_HI_RES, highResScrollX, eventTime) &&
+            writeInputEvent(EV_REL, REL_WHEEL_HI_RES, highResScrollY, eventTime);
+    if (!highResScrollResult) {
+        return false;
+    }
+
+    // According to evdev spec, a high-resolution mouse needs to emit REL_WHEEL / REL_HWHEEL events
+    // in addition to high-res scroll events. Regular scroll events can approximate high-res scroll
+    // events, so we send a regular scroll event when the accumulated scroll motion reaches a detent
+    // (single mouse wheel click).
+    mAccumulatedHighResScrollX += highResScrollX;
+    mAccumulatedHighResScrollY += highResScrollY;
+    const int32_t scrollX = mAccumulatedHighResScrollX / kEvdevMouseHighResScrollUnitsPerDetent;
+    const int32_t scrollY = mAccumulatedHighResScrollY / kEvdevMouseHighResScrollUnitsPerDetent;
+    if (scrollX != 0) {
+        if (!writeInputEvent(EV_REL, REL_HWHEEL, scrollX, eventTime)) {
+            return false;
+        }
+        mAccumulatedHighResScrollX %= kEvdevMouseHighResScrollUnitsPerDetent;
+    }
+    if (scrollY != 0) {
+        if (!writeInputEvent(EV_REL, REL_WHEEL, scrollY, eventTime)) {
+            return false;
+        }
+        mAccumulatedHighResScrollY %= kEvdevMouseHighResScrollUnitsPerDetent;
+    }
+
+    return writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime);
 }
 
 // --- VirtualTouchscreen ---
@@ -509,4 +549,15 @@
     return true;
 }
 
+// --- VirtualRotaryEncoder ---
+VirtualRotaryEncoder::VirtualRotaryEncoder(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
+
+VirtualRotaryEncoder::~VirtualRotaryEncoder() {}
+
+bool VirtualRotaryEncoder::writeScrollEvent(float scrollAmount,
+                                            std::chrono::nanoseconds eventTime) {
+    return writeInputEvent(EV_REL, REL_WHEEL, static_cast<int32_t>(scrollAmount), eventTime) &&
+            writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime);
+}
+
 } // namespace android
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index a77dfa5..e23fc94 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -49,130 +49,24 @@
     const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000;
 
     /**
-     * This flag indicates that the window that received this motion event is partly
-     * or wholly obscured by another visible window above it and the event directly passed through
-     * the obscured area.
-     *
-     * A security sensitive application can check this flag to identify situations in which
-     * a malicious application may have covered up part of its content for the purpose
-     * of misleading the user or hijacking touches.  An appropriate response might be
-     * to drop the suspect touches or to take additional precautions to confirm the user's
-     * actual intent.
-     */
-    const int MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1;
-
-    /**
-     * This flag indicates that the window that received this motion event is partly
-     * or wholly obscured by another visible window above it and the event did not directly pass
-     * through the obscured area.
-     *
-     * A security sensitive application can check this flag to identify situations in which
-     * a malicious application may have covered up part of its content for the purpose
-     * of misleading the user or hijacking touches.  An appropriate response might be
-     * to drop the suspect touches or to take additional precautions to confirm the user's
-     * actual intent.
-     *
-     * Unlike FLAG_WINDOW_IS_OBSCURED, this is only true if the window that received this event is
-     * obstructed in areas other than the touched location.
-     */
-    const int MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2;
-
-    /**
-     * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that
-     * this event will be immediately followed by a {@link #ACTION_HOVER_EXIT}. It is used to
-     * prevent generating redundant {@link #ACTION_HOVER_ENTER} events.
-     * @hide
-     */
-    const int MOTION_EVENT_FLAG_HOVER_EXIT_PENDING = 0x4;
-
-    /**
-     * This flag indicates that the event has been generated by a gesture generator. It
-     * provides a hint to the GestureDetector to not apply any touch slop.
-     *
-     * @hide
-     */
-    const int MOTION_EVENT_FLAG_IS_GENERATED_GESTURE = 0x8;
-
-    /**
-     * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}.
-     * It indicates that the pointer going up was an unintentional user touch. When FLAG_CANCELED
-     * is set, the typical actions that occur in response for a pointer going up (such as click
-     * handlers, end of drawing) should be aborted. This flag is typically set when the user was
-     * accidentally touching the screen, such as by gripping the device, or placing the palm on the
-     * screen.
-     *
-     * @see #ACTION_POINTER_UP
-     * @see #ACTION_CANCEL
+     * Common input event flag used for both motion and key events for a gesture or pointer being
+     * canceled.
      */
     const int INPUT_EVENT_FLAG_CANCELED = 0x20;
 
     /**
-     * This flag indicates that the event will not cause a focus change if it is directed to an
-     * unfocused window, even if it an {@link #ACTION_DOWN}. This is typically used with pointer
-     * gestures to allow the user to direct gestures to an unfocused window without bringing the
-     * window into focus.
-     * @hide
-     */
-    const int MOTION_EVENT_FLAG_NO_FOCUS_CHANGE = 0x40;
-
-    /**
-     * This flag indicates that the event has a valid value for AXIS_ORIENTATION.
-     *
-     * This is a private flag that is not used in Java.
-     * @hide
-     */
-    const int MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION = 0x80;
-
-    /**
-     * This flag indicates that the pointers' AXIS_ORIENTATION can be used to precisely determine
-     * the direction in which the tool is pointing. The value of the orientation axis will be in
-     * the range [-pi, pi], which represents a full circle. This is usually supported by devices
-     * like styluses.
-     *
-     * Conversely, AXIS_ORIENTATION cannot be used to tell which direction the tool is pointing
-     * when this flag is not set. In this case, the axis value will have a range of [-pi/2, pi/2],
-     * which represents half a circle. This is usually the case for devices like touchscreens and
-     * touchpads, for which it is difficult to tell which direction along the major axis of the
-     * touch ellipse the finger is pointing.
-     *
-     * This is a private flag that is not used in Java.
-     * @hide
-     */
-    const int MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = 0x100;
-
-    /**
-     * The input event was generated or modified by accessibility service.
-     * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either
-     * set of flags, including in input/Input.h and in android/input.h.
+     * Common input event flag used for both motion and key events, indicating that the event
+     * was generated or modified by accessibility service.
      */
     const int INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800;
 
     /**
-     * Private flag that indicates when the system has detected that this motion event
-     * may be inconsistent with respect to the sequence of previously delivered motion events,
-     * such as when a pointer move event is sent but the pointer is not down.
-     *
-     * @hide
-     * @see #isTainted
-     * @see #setTainted
+     * Common input event flag used for both motion and key events, indicating that the system has
+     * detected this event may be inconsistent with the current event sequence or gesture, such as
+     * when a pointer move event is sent but the pointer is not down.
      */
     const int INPUT_EVENT_FLAG_TAINTED = 0x80000000;
 
-    /**
-     * Private flag indicating that this event was synthesized by the system and should be delivered
-     * to the accessibility focused view first. When being dispatched such an event is not handled
-     * by predecessors of the accessibility focused view and after the event reaches that view the
-     * flag is cleared and normal event dispatch is performed. This ensures that the platform can
-     * click on any view that has accessibility focus which is semantically equivalent to asking the
-     * view to perform a click accessibility action but more generic as views not implementing click
-     * action correctly can still be activated.
-     *
-     * @hide
-     * @see #isTargetAccessibilityFocus()
-     * @see #setTargetAccessibilityFocus(boolean)
-     */
-    const int MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000;
-
     /* The default pointer acceleration value. */
     const int DEFAULT_POINTER_ACCELERATION = 3;
 
diff --git a/libs/input/android/os/MotionEventFlag.aidl b/libs/input/android/os/MotionEventFlag.aidl
new file mode 100644
index 0000000..2093b06
--- /dev/null
+++ b/libs/input/android/os/MotionEventFlag.aidl
@@ -0,0 +1,152 @@
+/**
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.IInputConstants;
+
+/**
+ * Flag definitions for MotionEvents.
+ * @hide
+ */
+@Backing(type="int")
+enum MotionEventFlag {
+
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it and the event directly passed through
+     * the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     */
+    WINDOW_IS_OBSCURED = 0x1,
+
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it and the event did not directly pass
+     * through the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     *
+     * Unlike FLAG_WINDOW_IS_OBSCURED, this is only true if the window that received this event is
+     * obstructed in areas other than the touched location.
+     */
+    WINDOW_IS_PARTIALLY_OBSCURED = 0x2,
+
+    /**
+     * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that
+     * this event will be immediately followed by a {@link #ACTION_HOVER_EXIT}. It is used to
+     * prevent generating redundant {@link #ACTION_HOVER_ENTER} events.
+     * @hide
+     */
+    HOVER_EXIT_PENDING = 0x4,
+
+    /**
+     * This flag indicates that the event has been generated by a gesture generator. It
+     * provides a hint to the GestureDetector to not apply any touch slop.
+     *
+     * @hide
+     */
+    IS_GENERATED_GESTURE = 0x8,
+
+    /**
+     * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}.
+     * It indicates that the pointer going up was an unintentional user touch. When FLAG_CANCELED
+     * is set, the typical actions that occur in response for a pointer going up (such as click
+     * handlers, end of drawing) should be aborted. This flag is typically set when the user was
+     * accidentally touching the screen, such as by gripping the device, or placing the palm on the
+     * screen.
+     *
+     * @see #ACTION_POINTER_UP
+     * @see #ACTION_CANCEL
+     */
+    CANCELED = IInputConstants.INPUT_EVENT_FLAG_CANCELED,
+
+    /**
+     * This flag indicates that the event will not cause a focus change if it is directed to an
+     * unfocused window, even if it an {@link #ACTION_DOWN}. This is typically used with pointer
+     * gestures to allow the user to direct gestures to an unfocused window without bringing the
+     * window into focus.
+     * @hide
+     */
+    NO_FOCUS_CHANGE = 0x40,
+
+    /**
+     * This flag indicates that the event has a valid value for AXIS_ORIENTATION.
+     *
+     * This is a private flag that is not used in Java.
+     * @hide
+     */
+    PRIVATE_FLAG_SUPPORTS_ORIENTATION = 0x80,
+
+    /**
+     * This flag indicates that the pointers' AXIS_ORIENTATION can be used to precisely determine
+     * the direction in which the tool is pointing. The value of the orientation axis will be in
+     * the range [-pi, pi], which represents a full circle. This is usually supported by devices
+     * like styluses.
+     *
+     * Conversely, AXIS_ORIENTATION cannot be used to tell which direction the tool is pointing
+     * when this flag is not set. In this case, the axis value will have a range of [-pi/2, pi/2],
+     * which represents half a circle. This is usually the case for devices like touchscreens and
+     * touchpads, for which it is difficult to tell which direction along the major axis of the
+     * touch ellipse the finger is pointing.
+     *
+     * This is a private flag that is not used in Java.
+     * @hide
+     */
+    PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = 0x100,
+
+    /**
+     * The input event was generated or modified by accessibility service.
+     * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either
+     * set of flags, including in input/Input.h and in android/input.h.
+     */
+    IS_ACCESSIBILITY_EVENT = IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+
+    /**
+     * Private flag that indicates when the system has detected that this motion event
+     * may be inconsistent with respect to the sequence of previously delivered motion events,
+     * such as when a pointer move event is sent but the pointer is not down.
+     *
+     * @hide
+     * @see #isTainted
+     * @see #setTainted
+     */
+    TAINTED = IInputConstants.INPUT_EVENT_FLAG_TAINTED,
+
+    /**
+     * Private flag indicating that this event was synthesized by the system and should be delivered
+     * to the accessibility focused view first. When being dispatched such an event is not handled
+     * by predecessors of the accessibility focused view and after the event reaches that view the
+     * flag is cleared and normal event dispatch is performed. This ensures that the platform can
+     * click on any view that has accessibility focus which is semantically equivalent to asking the
+     * view to perform a click accessibility action but more generic as views not implementing click
+     * action correctly can still be activated.
+     *
+     * @hide
+     * @see #isTargetAccessibilityFocus()
+     * @see #setTargetAccessibilityFocus(boolean)
+     */
+    TARGET_ACCESSIBILITY_FOCUS = 0x40000000,
+}
diff --git a/libs/input/rust/Android.bp b/libs/input/rust/Android.bp
index 018d199..63853f7 100644
--- a/libs/input/rust/Android.bp
+++ b/libs/input/rust/Android.bp
@@ -24,6 +24,8 @@
         "liblogger",
         "liblog_rust",
         "inputconstants-rust",
+        "libserde",
+        "libserde_json",
     ],
     whole_static_libs: [
         "libinput_from_rust_to_cpp",
diff --git a/libs/input/rust/data_store.rs b/libs/input/rust/data_store.rs
new file mode 100644
index 0000000..6bdcefd
--- /dev/null
+++ b/libs/input/rust/data_store.rs
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Contains the DataStore, used to store input related data in a persistent way.
+
+use crate::input::KeyboardType;
+use log::{debug, error};
+use serde::{Deserialize, Serialize};
+use std::fs::File;
+use std::io::{Read, Write};
+use std::path::Path;
+use std::sync::{Arc, RwLock};
+
+/// Data store to be used to store information that persistent across device reboots.
+pub struct DataStore {
+    file_reader_writer: Box<dyn FileReaderWriter>,
+    inner: Arc<RwLock<DataStoreInner>>,
+}
+
+#[derive(Default)]
+struct DataStoreInner {
+    is_loaded: bool,
+    data: Data,
+}
+
+#[derive(Default, Serialize, Deserialize)]
+struct Data {
+    // Map storing data for keyboard classification for specific devices.
+    #[serde(default)]
+    keyboard_classifications: Vec<KeyboardClassification>,
+    // NOTE: Important things to consider:
+    // - Add any data that needs to be persisted here in this struct.
+    // - Mark all new fields with "#[serde(default)]" for backward compatibility.
+    // - Also, you can't modify the already added fields.
+    // - Can add new nested fields to existing structs. e.g. Add another field to the struct
+    //   KeyboardClassification and mark it "#[serde(default)]".
+}
+
+#[derive(Default, Serialize, Deserialize)]
+struct KeyboardClassification {
+    descriptor: String,
+    keyboard_type: KeyboardType,
+    is_finalized: bool,
+}
+
+impl DataStore {
+    /// Creates a new instance of Data store
+    pub fn new(file_reader_writer: Box<dyn FileReaderWriter>) -> Self {
+        Self { file_reader_writer, inner: Default::default() }
+    }
+
+    fn load(&mut self) {
+        if self.inner.read().unwrap().is_loaded {
+            return;
+        }
+        self.load_internal();
+    }
+
+    fn load_internal(&mut self) {
+        let s = self.file_reader_writer.read();
+        let data: Data = if !s.is_empty() {
+            let deserialize: Data = match serde_json::from_str(&s) {
+                Ok(deserialize) => deserialize,
+                Err(msg) => {
+                    error!("Unable to deserialize JSON data into struct: {:?} -> {:?}", msg, s);
+                    Default::default()
+                }
+            };
+            deserialize
+        } else {
+            Default::default()
+        };
+
+        let mut inner = self.inner.write().unwrap();
+        inner.data = data;
+        inner.is_loaded = true;
+    }
+
+    fn save(&mut self) {
+        let string_to_save;
+        {
+            let inner = self.inner.read().unwrap();
+            string_to_save = serde_json::to_string(&inner.data).unwrap();
+        }
+        self.file_reader_writer.write(string_to_save);
+    }
+
+    /// Get keyboard type of the device (as stored in the data store)
+    pub fn get_keyboard_type(&mut self, descriptor: &String) -> Option<(KeyboardType, bool)> {
+        self.load();
+        let data = &self.inner.read().unwrap().data;
+        for keyboard_classification in data.keyboard_classifications.iter() {
+            if keyboard_classification.descriptor == *descriptor {
+                return Some((
+                    keyboard_classification.keyboard_type,
+                    keyboard_classification.is_finalized,
+                ));
+            }
+        }
+        None
+    }
+
+    /// Save keyboard type of the device in the data store
+    pub fn set_keyboard_type(
+        &mut self,
+        descriptor: &String,
+        keyboard_type: KeyboardType,
+        is_finalized: bool,
+    ) {
+        {
+            let data = &mut self.inner.write().unwrap().data;
+            data.keyboard_classifications
+                .retain(|classification| classification.descriptor != *descriptor);
+            data.keyboard_classifications.push(KeyboardClassification {
+                descriptor: descriptor.to_string(),
+                keyboard_type,
+                is_finalized,
+            })
+        }
+        self.save();
+    }
+}
+
+pub trait FileReaderWriter {
+    fn read(&self) -> String;
+    fn write(&self, to_write: String);
+}
+
+/// Default file reader writer implementation
+pub struct DefaultFileReaderWriter {
+    filepath: String,
+}
+
+impl DefaultFileReaderWriter {
+    /// Creates a new instance of Default file reader writer that can read and write string to a
+    /// particular file in the filesystem
+    pub fn new(filepath: String) -> Self {
+        Self { filepath }
+    }
+}
+
+impl FileReaderWriter for DefaultFileReaderWriter {
+    fn read(&self) -> String {
+        let path = Path::new(&self.filepath);
+        let mut fs_string = String::new();
+        match File::open(path) {
+            Err(e) => error!("couldn't open {:?}: {}", path, e),
+            Ok(mut file) => match file.read_to_string(&mut fs_string) {
+                Err(e) => error!("Couldn't read from {:?}: {}", path, e),
+                Ok(_) => debug!("Successfully read from file {:?}", path),
+            },
+        };
+        fs_string
+    }
+
+    fn write(&self, to_write: String) {
+        let path = Path::new(&self.filepath);
+        match File::create(path) {
+            Err(e) => error!("couldn't create {:?}: {}", path, e),
+            Ok(mut file) => match file.write_all(to_write.as_bytes()) {
+                Err(e) => error!("Couldn't write to {:?}: {}", path, e),
+                Ok(_) => debug!("Successfully saved to file {:?}", path),
+            },
+        };
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::data_store::{
+        test_file_reader_writer::TestFileReaderWriter, DataStore, FileReaderWriter,
+    };
+    use crate::input::KeyboardType;
+
+    #[test]
+    fn test_backward_compatibility_version_1() {
+        // This test tests JSON string that will be created by the first version of data store
+        // This test SHOULD NOT be modified
+        let test_reader_writer = TestFileReaderWriter::new();
+        test_reader_writer.write(r#"{"keyboard_classifications":[{"descriptor":"descriptor","keyboard_type":{"type":"Alphabetic"},"is_finalized":true}]}"#.to_string());
+
+        let mut data_store = DataStore::new(Box::new(test_reader_writer));
+        let (keyboard_type, is_finalized) =
+            data_store.get_keyboard_type(&"descriptor".to_string()).unwrap();
+        assert_eq!(keyboard_type, KeyboardType::Alphabetic);
+        assert!(is_finalized);
+    }
+}
+
+#[cfg(test)]
+pub mod test_file_reader_writer {
+
+    use crate::data_store::FileReaderWriter;
+    use std::sync::{Arc, RwLock};
+
+    #[derive(Default)]
+    struct TestFileReaderWriterInner {
+        fs_string: String,
+    }
+
+    #[derive(Default, Clone)]
+    pub struct TestFileReaderWriter(Arc<RwLock<TestFileReaderWriterInner>>);
+
+    impl TestFileReaderWriter {
+        pub fn new() -> Self {
+            Default::default()
+        }
+    }
+
+    impl FileReaderWriter for TestFileReaderWriter {
+        fn read(&self) -> String {
+            self.0.read().unwrap().fs_string.clone()
+        }
+
+        fn write(&self, fs_string: String) {
+            self.0.write().unwrap().fs_string = fs_string;
+        }
+    }
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 564d94d..90f509d 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -19,6 +19,8 @@
 use crate::ffi::RustInputDeviceIdentifier;
 use bitflags::bitflags;
 use inputconstants::aidl::android::os::IInputConstants;
+use inputconstants::aidl::android::os::MotionEventFlag::MotionEventFlag;
+use serde::{Deserialize, Serialize};
 use std::fmt;
 
 /// The InputDevice ID.
@@ -193,31 +195,34 @@
 
 bitflags! {
     /// MotionEvent flags.
+    /// The source of truth for the flag definitions are the MotionEventFlag AIDL enum.
+    /// The flag values are redefined here as a bitflags API.
     #[derive(Debug)]
     pub struct MotionFlags: u32 {
         /// FLAG_WINDOW_IS_OBSCURED
-        const WINDOW_IS_OBSCURED = IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED as u32;
+        const WINDOW_IS_OBSCURED = MotionEventFlag::WINDOW_IS_OBSCURED.0 as u32;
         /// FLAG_WINDOW_IS_PARTIALLY_OBSCURED
-        const WINDOW_IS_PARTIALLY_OBSCURED = IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED as u32;
+        const WINDOW_IS_PARTIALLY_OBSCURED = MotionEventFlag::WINDOW_IS_PARTIALLY_OBSCURED.0 as u32;
         /// FLAG_HOVER_EXIT_PENDING
-        const HOVER_EXIT_PENDING = IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING as u32;
+        const HOVER_EXIT_PENDING = MotionEventFlag::HOVER_EXIT_PENDING.0 as u32;
         /// FLAG_IS_GENERATED_GESTURE
-        const IS_GENERATED_GESTURE = IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE as u32;
+        const IS_GENERATED_GESTURE = MotionEventFlag::IS_GENERATED_GESTURE.0 as u32;
         /// FLAG_CANCELED
-        const CANCELED = IInputConstants::INPUT_EVENT_FLAG_CANCELED as u32;
+        const CANCELED = MotionEventFlag::CANCELED.0 as u32;
         /// FLAG_NO_FOCUS_CHANGE
-        const NO_FOCUS_CHANGE = IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32;
+        const NO_FOCUS_CHANGE = MotionEventFlag::NO_FOCUS_CHANGE.0 as u32;
         /// PRIVATE_FLAG_SUPPORTS_ORIENTATION
-        const PRIVATE_SUPPORTS_ORIENTATION = IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION as u32;
+        const PRIVATE_FLAG_SUPPORTS_ORIENTATION =
+                MotionEventFlag::PRIVATE_FLAG_SUPPORTS_ORIENTATION.0 as u32;
         /// PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION
-        const PRIVATE_SUPPORTS_DIRECTIONAL_ORIENTATION =
-                IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION as u32;
+        const PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION =
+                MotionEventFlag::PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION.0 as u32;
         /// FLAG_IS_ACCESSIBILITY_EVENT
-        const IS_ACCESSIBILITY_EVENT = IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32;
+        const IS_ACCESSIBILITY_EVENT = MotionEventFlag::IS_ACCESSIBILITY_EVENT.0 as u32;
         /// FLAG_TAINTED
-        const TAINTED = IInputConstants::INPUT_EVENT_FLAG_TAINTED as u32;
+        const TAINTED = MotionEventFlag::TAINTED.0 as u32;
         /// FLAG_TARGET_ACCESSIBILITY_FOCUS
-        const TARGET_ACCESSIBILITY_FOCUS = IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS as u32;
+        const TARGET_ACCESSIBILITY_FOCUS = MotionEventFlag::TARGET_ACCESSIBILITY_FOCUS.0 as u32;
     }
 }
 
@@ -320,9 +325,11 @@
 
 /// A rust enum representation of a Keyboard type.
 #[repr(u32)]
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
+#[serde(tag = "type")]
 pub enum KeyboardType {
     /// KEYBOARD_TYPE_NONE
+    #[default]
     None = input_bindgen::AINPUT_KEYBOARD_TYPE_NONE,
     /// KEYBOARD_TYPE_NON_ALPHABETIC
     NonAlphabetic = input_bindgen::AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
@@ -333,10 +340,24 @@
 #[cfg(test)]
 mod tests {
     use crate::input::SourceClass;
+    use crate::MotionFlags;
     use crate::Source;
+    use inputconstants::aidl::android::os::MotionEventFlag::MotionEventFlag;
+
     #[test]
     fn convert_source_class_pointer() {
         let source = Source::from_bits(input_bindgen::AINPUT_SOURCE_CLASS_POINTER).unwrap();
         assert!(source.is_from_class(SourceClass::Pointer));
     }
+
+    /// Ensure all MotionEventFlag constants are re-defined in rust.
+    #[test]
+    fn all_motion_event_flags_defined() {
+        for flag in MotionEventFlag::enum_values() {
+            assert!(
+                MotionFlags::from_bits(flag.0 as u32).is_some(),
+                "MotionEventFlag value {flag:?} is not redefined as a rust MotionFlags"
+            );
+        }
+    }
 }
diff --git a/libs/input/rust/keyboard_classification_config.rs b/libs/input/rust/keyboard_classification_config.rs
new file mode 100644
index 0000000..ab74efb
--- /dev/null
+++ b/libs/input/rust/keyboard_classification_config.rs
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::input::KeyboardType;
+
+// TODO(b/263559234): Categorize some of these to KeyboardType::None based on ability to produce
+//  key events at all. (Requires setup allowing InputDevice to dynamically add/remove
+//  KeyboardInputMapper based on blocklist and KeyEvents in case a KeyboardType::None device ends
+//  up producing a key event)
+pub static CLASSIFIED_DEVICES: &[(
+    /* vendorId */ u16,
+    /* productId */ u16,
+    KeyboardType,
+    /* is_finalized */ bool,
+)] = &[
+    // HP X4000 Wireless Mouse
+    (0x03f0, 0xa407, KeyboardType::NonAlphabetic, true),
+    // Microsoft Wireless Mobile Mouse 6000
+    (0x045e, 0x0745, KeyboardType::NonAlphabetic, true),
+    // Microsoft Surface Precision Mouse
+    (0x045e, 0x0821, KeyboardType::NonAlphabetic, true),
+    // Microsoft Pro IntelliMouse
+    (0x045e, 0x082a, KeyboardType::NonAlphabetic, true),
+    // Microsoft Bluetooth Mouse
+    (0x045e, 0x082f, KeyboardType::NonAlphabetic, true),
+    // Xbox One Elite Series 2 gamepad
+    (0x045e, 0x0b05, KeyboardType::NonAlphabetic, true),
+    // Logitech T400
+    (0x046d, 0x4026, KeyboardType::NonAlphabetic, true),
+    // Logitech M720 Triathlon (Unifying)
+    (0x046d, 0x405e, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master 2S (Unifying)
+    (0x046d, 0x4069, KeyboardType::NonAlphabetic, true),
+    // Logitech M585 (Unifying)
+    (0x046d, 0x406b, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2 (Unifying)
+    (0x046d, 0x4072, KeyboardType::NonAlphabetic, true),
+    // Logitech Pebble M350
+    (0x046d, 0x4080, KeyboardType::NonAlphabetic, true),
+    // Logitech T630 Ultrathin
+    (0x046d, 0xb00d, KeyboardType::NonAlphabetic, true),
+    // Logitech M558
+    (0x046d, 0xb011, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master (Bluetooth)
+    (0x046d, 0xb012, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2 (Bluetooth)
+    (0x046d, 0xb013, KeyboardType::NonAlphabetic, true),
+    // Logitech M720 Triathlon (Bluetooth)
+    (0x046d, 0xb015, KeyboardType::NonAlphabetic, true),
+    // Logitech M535
+    (0x046d, 0xb016, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master / Anywhere 2 (Bluetooth)
+    (0x046d, 0xb017, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master 2S (Bluetooth)
+    (0x046d, 0xb019, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2S (Bluetooth)
+    (0x046d, 0xb01a, KeyboardType::NonAlphabetic, true),
+    // Logitech M585/M590 (Bluetooth)
+    (0x046d, 0xb01b, KeyboardType::NonAlphabetic, true),
+    // Logitech G603 Lightspeed Gaming Mouse (Bluetooth)
+    (0x046d, 0xb01c, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master (Bluetooth)
+    (0x046d, 0xb01e, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2 (Bluetooth)
+    (0x046d, 0xb01f, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master 3 (Bluetooth)
+    (0x046d, 0xb023, KeyboardType::NonAlphabetic, true),
+    // Logitech G604 Lightspeed Gaming Mouse (Bluetooth)
+    (0x046d, 0xb024, KeyboardType::NonAlphabetic, true),
+    // Logitech Spotlight Presentation Remote (Bluetooth)
+    (0x046d, 0xb503, KeyboardType::NonAlphabetic, true),
+    // Logitech R500 (Bluetooth)
+    (0x046d, 0xb505, KeyboardType::NonAlphabetic, true),
+    // Logitech M500s
+    (0x046d, 0xc093, KeyboardType::NonAlphabetic, true),
+    // Logitech Spotlight Presentation Remote (USB dongle)
+    (0x046d, 0xc53e, KeyboardType::NonAlphabetic, true),
+    // Elecom Enelo IR LED Mouse 350
+    (0x056e, 0x0134, KeyboardType::NonAlphabetic, true),
+    // Elecom EPRIM Blue LED 5 button mouse 228
+    (0x056e, 0x0141, KeyboardType::NonAlphabetic, true),
+    // Elecom Blue LED Mouse 203
+    (0x056e, 0x0159, KeyboardType::NonAlphabetic, true),
+    // Zebra LS2208 barcode scanner
+    (0x05e0, 0x1200, KeyboardType::NonAlphabetic, true),
+    // RDing FootSwitch1F1
+    (0x0c45, 0x7403, KeyboardType::NonAlphabetic, true),
+    // SteelSeries Sensei RAW Frost Blue
+    (0x1038, 0x1369, KeyboardType::NonAlphabetic, true),
+    // SteelSeries Rival 3 Wired
+    (0x1038, 0x1824, KeyboardType::NonAlphabetic, true),
+    // SteelSeries Rival 3 Wireless (USB dongle)
+    (0x1038, 0x1830, KeyboardType::NonAlphabetic, true),
+    // Yubico.com Yubikey
+    (0x1050, 0x0010, KeyboardType::NonAlphabetic, true),
+    // Yubico.com Yubikey 4 OTP+U2F+CCID
+    (0x1050, 0x0407, KeyboardType::NonAlphabetic, true),
+    // Lenovo USB-C Wired Compact Mouse
+    (0x17ef, 0x6123, KeyboardType::NonAlphabetic, true),
+    // Corsair Katar Pro Wireless (USB dongle)
+    (0x1b1c, 0x1b94, KeyboardType::NonAlphabetic, true),
+    // Corsair Katar Pro Wireless (Bluetooth)
+    (0x1bae, 0x1b1c, KeyboardType::NonAlphabetic, true),
+    // Kensington Pro Fit Full-size
+    (0x1bcf, 0x08a0, KeyboardType::NonAlphabetic, true),
+    // Huion HS64
+    (0x256c, 0x006d, KeyboardType::NonAlphabetic, true),
+    // XP-Pen Star G640
+    (0x28bd, 0x0914, KeyboardType::NonAlphabetic, true),
+    // XP-Pen Artist 12 Pro
+    (0x28bd, 0x091f, KeyboardType::NonAlphabetic, true),
+    // XP-Pen Deco mini7W
+    (0x28bd, 0x0928, KeyboardType::NonAlphabetic, true),
+];
diff --git a/libs/input/rust/keyboard_classifier.rs b/libs/input/rust/keyboard_classifier.rs
index 1063fac..3c789b4 100644
--- a/libs/input/rust/keyboard_classifier.rs
+++ b/libs/input/rust/keyboard_classifier.rs
@@ -31,39 +31,37 @@
 //!    across multiple device connections in a time period, then change type to
 //!    KeyboardType::NonAlphabetic. Once changed, it can still change back to Alphabetic
 //!    (i.e. verified = false).
-//!
-//! TODO(b/263559234): Data store implementation to store information about past classification
 
+use crate::data_store::DataStore;
 use crate::input::{DeviceId, InputDevice, KeyboardType};
+use crate::keyboard_classification_config::CLASSIFIED_DEVICES;
 use crate::{DeviceClass, ModifierState};
 use std::collections::HashMap;
 
 /// The KeyboardClassifier is used to classify a keyboard device into non-keyboard, alphabetic
 /// keyboard or non-alphabetic keyboard
-#[derive(Default)]
 pub struct KeyboardClassifier {
     device_map: HashMap<DeviceId, KeyboardInfo>,
+    data_store: DataStore,
 }
 
 struct KeyboardInfo {
-    _device: InputDevice,
+    device: InputDevice,
     keyboard_type: KeyboardType,
     is_finalized: bool,
 }
 
 impl KeyboardClassifier {
     /// Create a new KeyboardClassifier
-    pub fn new() -> Self {
-        Default::default()
+    pub fn new(data_store: DataStore) -> Self {
+        Self { device_map: HashMap::new(), data_store }
     }
 
     /// Adds keyboard to KeyboardClassifier
     pub fn notify_keyboard_changed(&mut self, device: InputDevice) {
         let (keyboard_type, is_finalized) = self.classify_keyboard(&device);
-        self.device_map.insert(
-            device.device_id,
-            KeyboardInfo { _device: device, keyboard_type, is_finalized },
-        );
+        self.device_map
+            .insert(device.device_id, KeyboardInfo { device, keyboard_type, is_finalized });
     }
 
     /// Get keyboard type for a tracked keyboard in KeyboardClassifier
@@ -106,11 +104,16 @@
             if Self::is_alphabetic_key(&evdev_code) {
                 keyboard.keyboard_type = KeyboardType::Alphabetic;
                 keyboard.is_finalized = true;
+                self.data_store.set_keyboard_type(
+                    &keyboard.device.identifier.descriptor,
+                    keyboard.keyboard_type,
+                    keyboard.is_finalized,
+                );
             }
         }
     }
 
-    fn classify_keyboard(&self, device: &InputDevice) -> (KeyboardType, bool) {
+    fn classify_keyboard(&mut self, device: &InputDevice) -> (KeyboardType, bool) {
         // This should never happen but having keyboard device class is necessary to be classified
         // as any type of keyboard.
         if !device.classes.contains(DeviceClass::Keyboard) {
@@ -126,6 +129,21 @@
                 (KeyboardType::NonAlphabetic, true)
             };
         }
+
+        // Check in data store
+        if let Some((keyboard_type, is_finalized)) =
+            self.data_store.get_keyboard_type(&device.identifier.descriptor)
+        {
+            return (keyboard_type, is_finalized);
+        }
+
+        // Check in known device list for classification
+        for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() {
+            if device.identifier.vendor == *vendor && device.identifier.product == *product {
+                return (*keyboard_type, *is_finalized);
+            }
+        }
+
         // Any composite device with multiple device classes should be categorized as non-alphabetic
         // keyboard initially
         if device.classes.contains(DeviceClass::Touch)
@@ -168,17 +186,20 @@
 
 #[cfg(test)]
 mod tests {
+    use crate::data_store::{test_file_reader_writer::TestFileReaderWriter, DataStore};
     use crate::input::{DeviceId, InputDevice, KeyboardType};
+    use crate::keyboard_classification_config::CLASSIFIED_DEVICES;
     use crate::keyboard_classifier::KeyboardClassifier;
     use crate::{DeviceClass, ModifierState, RustInputDeviceIdentifier};
 
     static DEVICE_ID: DeviceId = DeviceId(1);
+    static SECOND_DEVICE_ID: DeviceId = DeviceId(2);
     static KEY_A: i32 = 30;
     static KEY_1: i32 = 2;
 
     #[test]
     fn classify_external_alphabetic_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External,
         ));
@@ -188,7 +209,7 @@
 
     #[test]
     fn classify_external_non_alphabetic_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier
             .notify_keyboard_changed(create_device(DeviceClass::Keyboard | DeviceClass::External));
         assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
@@ -197,7 +218,7 @@
 
     #[test]
     fn classify_mouse_pretending_as_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Cursor
@@ -210,7 +231,7 @@
 
     #[test]
     fn classify_touchpad_pretending_as_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Touchpad
@@ -223,7 +244,7 @@
 
     #[test]
     fn classify_stylus_pretending_as_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::ExternalStylus
@@ -236,7 +257,7 @@
 
     #[test]
     fn classify_dpad_pretending_as_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Dpad
@@ -249,7 +270,7 @@
 
     #[test]
     fn classify_joystick_pretending_as_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Joystick
@@ -262,7 +283,7 @@
 
     #[test]
     fn classify_gamepad_pretending_as_keyboard() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Gamepad
@@ -275,7 +296,7 @@
 
     #[test]
     fn reclassify_keyboard_on_alphabetic_key_event() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Dpad
@@ -293,7 +314,7 @@
 
     #[test]
     fn dont_reclassify_keyboard_on_non_alphabetic_key_event() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Dpad
@@ -311,7 +332,7 @@
 
     #[test]
     fn dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers() {
-        let mut classifier = KeyboardClassifier::new();
+        let mut classifier = create_classifier();
         classifier.notify_keyboard_changed(create_device(
             DeviceClass::Keyboard
                 | DeviceClass::Dpad
@@ -326,20 +347,82 @@
         assert!(!classifier.is_finalized(DEVICE_ID));
     }
 
+    #[test]
+    fn classify_known_devices() {
+        let mut classifier = create_classifier();
+        for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() {
+            classifier
+                .notify_keyboard_changed(create_device_with_vendor_product_ids(*vendor, *product));
+            assert_eq!(classifier.get_keyboard_type(DEVICE_ID), *keyboard_type);
+            assert_eq!(classifier.is_finalized(DEVICE_ID), *is_finalized);
+        }
+    }
+
+    #[test]
+    fn classify_previously_reclassified_devices() {
+        let test_reader_writer = TestFileReaderWriter::new();
+        {
+            let mut classifier =
+                KeyboardClassifier::new(DataStore::new(Box::new(test_reader_writer.clone())));
+            let device = create_device(
+                DeviceClass::Keyboard
+                    | DeviceClass::Dpad
+                    | DeviceClass::AlphabeticKey
+                    | DeviceClass::External,
+            );
+            classifier.notify_keyboard_changed(device);
+            classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None);
+        }
+
+        // Re-create classifier and data store to mimic a reboot (but use the same file system
+        // reader writer)
+        {
+            let mut classifier =
+                KeyboardClassifier::new(DataStore::new(Box::new(test_reader_writer.clone())));
+            let device = InputDevice {
+                device_id: SECOND_DEVICE_ID,
+                identifier: create_identifier(/* vendor= */ 234, /* product= */ 345),
+                classes: DeviceClass::Keyboard
+                    | DeviceClass::Dpad
+                    | DeviceClass::AlphabeticKey
+                    | DeviceClass::External,
+            };
+            classifier.notify_keyboard_changed(device);
+            assert_eq!(classifier.get_keyboard_type(SECOND_DEVICE_ID), KeyboardType::Alphabetic);
+            assert!(classifier.is_finalized(SECOND_DEVICE_ID));
+        }
+    }
+
+    fn create_classifier() -> KeyboardClassifier {
+        KeyboardClassifier::new(DataStore::new(Box::new(TestFileReaderWriter::new())))
+    }
+
+    fn create_identifier(vendor: u16, product: u16) -> RustInputDeviceIdentifier {
+        RustInputDeviceIdentifier {
+            name: "test_device".to_string(),
+            location: "location".to_string(),
+            unique_id: "unique_id".to_string(),
+            bus: 123,
+            vendor,
+            product,
+            version: 567,
+            descriptor: "descriptor".to_string(),
+        }
+    }
+
     fn create_device(classes: DeviceClass) -> InputDevice {
         InputDevice {
             device_id: DEVICE_ID,
-            identifier: RustInputDeviceIdentifier {
-                name: "test_device".to_string(),
-                location: "location".to_string(),
-                unique_id: "unique_id".to_string(),
-                bus: 123,
-                vendor: 234,
-                product: 345,
-                version: 567,
-                descriptor: "descriptor".to_string(),
-            },
+            identifier: create_identifier(/* vendor= */ 234, /* product= */ 345),
             classes,
         }
     }
+
+    fn create_device_with_vendor_product_ids(vendor: u16, product: u16) -> InputDevice {
+        InputDevice {
+            device_id: DEVICE_ID,
+            identifier: create_identifier(vendor, product),
+            classes: DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External,
+        }
+    }
 }
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index 5010475..008f675 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -16,10 +16,13 @@
 
 //! The rust component of libinput.
 
+mod data_store;
 mod input;
 mod input_verifier;
+mod keyboard_classification_config;
 mod keyboard_classifier;
 
+pub use data_store::{DataStore, DefaultFileReaderWriter};
 pub use input::{
     DeviceClass, DeviceId, InputDevice, ModifierState, MotionAction, MotionFlags, Source,
 };
@@ -148,7 +151,14 @@
 }
 
 fn create_keyboard_classifier() -> Box<KeyboardClassifier> {
-    Box::new(KeyboardClassifier::new())
+    // Future design: Make this data store singleton by passing it to C++ side and making it global
+    // and pass by reference to components that need to store persistent data.
+    //
+    // Currently only used by rust keyboard classifier so keeping it here.
+    let data_store = DataStore::new(Box::new(DefaultFileReaderWriter::new(
+        "/data/system/inputflinger-data.json".to_string(),
+    )));
+    Box::new(KeyboardClassifier::new(data_store))
 }
 
 fn notify_keyboard_changed(
diff --git a/libs/input/tests/InputChannel_test.cpp b/libs/input/tests/InputChannel_test.cpp
index 02d4c07..25356cf 100644
--- a/libs/input/tests/InputChannel_test.cpp
+++ b/libs/input/tests/InputChannel_test.cpp
@@ -65,11 +65,7 @@
 
     ASSERT_EQ(OK, result) << "should have successfully opened a channel pair";
 
-    // Name
-    EXPECT_STREQ("channel name (server)", serverChannel->getName().c_str())
-            << "server channel should have suffixed name";
-    EXPECT_STREQ("channel name (client)", clientChannel->getName().c_str())
-            << "client channel should have suffixed name";
+    EXPECT_EQ(serverChannel->getName(), clientChannel->getName());
 
     // Server->Client communication
     InputMessage serverMsg = {};
@@ -78,9 +74,10 @@
     EXPECT_EQ(OK, serverChannel->sendMessage(&serverMsg))
             << "server channel should be able to send message to client channel";
 
-    InputMessage clientMsg;
-    EXPECT_EQ(OK, clientChannel->receiveMessage(&clientMsg))
+    android::base::Result<InputMessage> clientMsgResult = clientChannel->receiveMessage();
+    ASSERT_TRUE(clientMsgResult.ok())
             << "client channel should be able to receive message from server channel";
+    const InputMessage& clientMsg = *clientMsgResult;
     EXPECT_EQ(serverMsg.header.type, clientMsg.header.type)
             << "client channel should receive the correct message from server channel";
     EXPECT_EQ(serverMsg.body.key.action, clientMsg.body.key.action)
@@ -94,9 +91,10 @@
     EXPECT_EQ(OK, clientChannel->sendMessage(&clientReply))
             << "client channel should be able to send message to server channel";
 
-    InputMessage serverReply;
-    EXPECT_EQ(OK, serverChannel->receiveMessage(&serverReply))
+    android::base::Result<InputMessage> serverReplyResult = serverChannel->receiveMessage();
+    ASSERT_TRUE(serverReplyResult.ok())
             << "server channel should be able to receive message from client channel";
+    const InputMessage& serverReply = *serverReplyResult;
     EXPECT_EQ(clientReply.header.type, serverReply.header.type)
             << "server channel should receive the correct message from client channel";
     EXPECT_EQ(clientReply.header.seq, serverReply.header.seq)
@@ -134,9 +132,10 @@
             << "client channel should observe that message is available before receiving it";
 
     // Receive (consume) the message.
-    InputMessage clientMsg;
-    EXPECT_EQ(OK, receiverChannel->receiveMessage(&clientMsg))
+    android::base::Result<InputMessage> clientMsgResult = receiverChannel->receiveMessage();
+    ASSERT_TRUE(clientMsgResult.ok())
             << "client channel should be able to receive message from server channel";
+    const InputMessage& clientMsg = *clientMsgResult;
     EXPECT_EQ(serverMsg.header.type, clientMsg.header.type)
             << "client channel should receive the correct message from server channel";
     EXPECT_EQ(serverMsg.body.key.action, clientMsg.body.key.action)
@@ -156,8 +155,8 @@
     ASSERT_EQ(OK, result)
             << "should have successfully opened a channel pair";
 
-    InputMessage msg;
-    EXPECT_EQ(WOULD_BLOCK, clientChannel->receiveMessage(&msg))
+    android::base::Result<InputMessage> msgResult = clientChannel->receiveMessage();
+    EXPECT_EQ(WOULD_BLOCK, msgResult.error().code())
             << "receiveMessage should have returned WOULD_BLOCK";
 }
 
@@ -172,8 +171,8 @@
 
     serverChannel.reset(); // close server channel
 
-    InputMessage msg;
-    EXPECT_EQ(DEAD_OBJECT, clientChannel->receiveMessage(&msg))
+    android::base::Result<InputMessage> msgResult = clientChannel->receiveMessage();
+    EXPECT_EQ(DEAD_OBJECT, msgResult.error().code())
             << "receiveMessage should have returned DEAD_OBJECT";
 }
 
@@ -207,7 +206,7 @@
         MotionClassification::DEEP_PRESS,
     };
 
-    InputMessage serverMsg = {}, clientMsg;
+    InputMessage serverMsg = {};
     serverMsg.header.type = InputMessage::Type::MOTION;
     serverMsg.header.seq = 1;
     serverMsg.body.motion.pointerCount = 1;
@@ -218,11 +217,13 @@
         EXPECT_EQ(OK, serverChannel->sendMessage(&serverMsg))
                 << "server channel should be able to send message to client channel";
 
-        EXPECT_EQ(OK, clientChannel->receiveMessage(&clientMsg))
+        android::base::Result<InputMessage> clientMsgResult = clientChannel->receiveMessage();
+        ASSERT_TRUE(clientMsgResult.ok())
                 << "client channel should be able to receive message from server channel";
+        const InputMessage& clientMsg = *clientMsgResult;
         EXPECT_EQ(serverMsg.header.type, clientMsg.header.type);
-        EXPECT_EQ(classification, clientMsg.body.motion.classification) <<
-                "Expected to receive " << motionClassificationToString(classification);
+        EXPECT_EQ(classification, clientMsg.body.motion.classification)
+                << "Expected to receive " << motionClassificationToString(classification);
     }
 }
 
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
index f49469c..e710613 100644
--- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -52,7 +52,7 @@
     const int32_t action;
     const nsecs_t downTime;
     const uint32_t seq;
-    const int32_t eventId;
+    int32_t eventId;
     const int32_t deviceId = 1;
     const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
     const ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
@@ -291,6 +291,7 @@
     void publishAndConsumeKeyEvent();
     void publishAndConsumeMotionStream();
     void publishAndConsumeMotionDown(nsecs_t downTime);
+    void publishAndConsumeSinglePointerMultipleSamples(const size_t nSamples);
     void publishAndConsumeBatchedMotionMove(nsecs_t downTime);
     void publishAndConsumeFocusEvent();
     void publishAndConsumeCaptureEvent();
@@ -298,6 +299,7 @@
     void publishAndConsumeTouchModeEvent();
     void publishAndConsumeMotionEvent(int32_t action, nsecs_t downTime,
                                       const std::vector<Pointer>& pointers);
+
     void TearDown() override {
         // Destroy the consumer, flushing any of the pending ack's.
         sendMessage(LooperMessage::DESTROY_CONSUMER);
@@ -519,6 +521,123 @@
                                  {Pointer{.id = 0, .x = 20, .y = 30}});
 }
 
+/*
+ * Decompose a potential multi-sampled MotionEvent into multiple MotionEvents
+ * with a single sample.
+ */
+std::vector<MotionEvent> splitBatchedMotionEvent(const MotionEvent& batchedMotionEvent) {
+    std::vector<MotionEvent> singleMotionEvents;
+    const size_t batchSize = batchedMotionEvent.getHistorySize() + 1;
+    for (size_t i = 0; i < batchSize; ++i) {
+        MotionEvent singleMotionEvent;
+        singleMotionEvent
+                .initialize(batchedMotionEvent.getId(), batchedMotionEvent.getDeviceId(),
+                            batchedMotionEvent.getSource(), batchedMotionEvent.getDisplayId(),
+                            batchedMotionEvent.getHmac(), batchedMotionEvent.getAction(),
+                            batchedMotionEvent.getActionButton(), batchedMotionEvent.getFlags(),
+                            batchedMotionEvent.getEdgeFlags(), batchedMotionEvent.getMetaState(),
+                            batchedMotionEvent.getButtonState(),
+                            batchedMotionEvent.getClassification(),
+                            batchedMotionEvent.getTransform(), batchedMotionEvent.getXPrecision(),
+                            batchedMotionEvent.getYPrecision(),
+                            batchedMotionEvent.getRawXCursorPosition(),
+                            batchedMotionEvent.getRawYCursorPosition(),
+                            batchedMotionEvent.getRawTransform(), batchedMotionEvent.getDownTime(),
+                            batchedMotionEvent.getHistoricalEventTime(/*historicalIndex=*/i),
+                            batchedMotionEvent.getPointerCount(),
+                            batchedMotionEvent.getPointerProperties(),
+                            (batchedMotionEvent.getSamplePointerCoords() + i));
+        singleMotionEvents.push_back(singleMotionEvent);
+    }
+    return singleMotionEvents;
+}
+
+/*
+ * Simulates a single pointer touching the screen and leaving it there for a period of time.
+ * Publishes a DOWN event and consumes it right away. Then, publishes a sequence of MOVE
+ * samples for the same pointer, and waits until it has been consumed. Splits batched MotionEvents
+ * into individual samples. Checks the consumed MotionEvents against the published ones.
+ * This test is non-deterministic because it depends on the timing of arrival of events to the
+ * socket.
+ *
+ * @param nSamples The number of MOVE samples to publish before attempting consumption.
+ */
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeSinglePointerMultipleSamples(
+        const size_t nSamples) {
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    const Pointer pointer(0, 20, 30);
+
+    const PublishMotionArgs argsDown(AMOTION_EVENT_ACTION_DOWN, downTime, {pointer}, mSeq);
+    const nsecs_t publishTimeOfDown = systemTime(SYSTEM_TIME_MONOTONIC);
+    publishMotionEvent(*mPublisher, argsDown);
+
+    // Consume the DOWN event.
+    ASSERT_TRUE(mMotionEvents.popWithTimeout(TIMEOUT).has_value());
+
+    verifyFinishedSignal(*mPublisher, mSeq, publishTimeOfDown);
+
+    std::vector<nsecs_t> publishTimes;
+    std::vector<PublishMotionArgs> argsMoves;
+    std::queue<uint32_t> publishedSequenceNumbers;
+
+    // Block Looper to increase the chance of batching events
+    {
+        std::scoped_lock l(mLock);
+        mLooperMayProceed = false;
+    }
+    sendMessage(LooperMessage::BLOCK_LOOPER);
+    {
+        std::unique_lock l(mLock);
+        mNotifyLooperWaiting.wait(l, [this] { return mLooperIsBlocked; });
+    }
+
+    uint32_t firstSampleId;
+    for (size_t i = 0; i < nSamples; ++i) {
+        publishedSequenceNumbers.push(++mSeq);
+        PublishMotionArgs argsMove(AMOTION_EVENT_ACTION_MOVE, downTime, {pointer}, mSeq);
+        // A batched MotionEvent only has a single event id, currently determined when the
+        // MotionEvent is initialized. Therefore, to pass the eventId comparisons inside
+        // verifyArgsEqualToEvent, we need to override the event id of the published args to match
+        // the event id of the first sample inside the MotionEvent.
+        if (i == 0) {
+            firstSampleId = argsMove.eventId;
+        }
+        argsMove.eventId = firstSampleId;
+        publishTimes.push_back(systemTime(SYSTEM_TIME_MONOTONIC));
+        argsMoves.push_back(argsMove);
+        publishMotionEvent(*mPublisher, argsMove);
+    }
+
+    std::vector<MotionEvent> singleSampledMotionEvents;
+
+    // Unblock Looper
+    {
+        std::scoped_lock l(mLock);
+        mLooperMayProceed = true;
+    }
+    mNotifyLooperMayProceed.notify_all();
+
+    // We have no control over the socket behavior, so the consumer can receive
+    // the motion as a batched event, or as a sequence of multiple single-sample MotionEvents (or a
+    // mix of those)
+    while (singleSampledMotionEvents.size() != nSamples) {
+        const std::optional<std::unique_ptr<MotionEvent>> batchedMotionEvent =
+                mMotionEvents.popWithTimeout(TIMEOUT);
+        // The events received by these calls are never null
+        std::vector<MotionEvent> splitMotionEvents = splitBatchedMotionEvent(**batchedMotionEvent);
+        singleSampledMotionEvents.insert(singleSampledMotionEvents.end(), splitMotionEvents.begin(),
+                                         splitMotionEvents.end());
+    }
+
+    // Consumer can choose to finish events in any order. For simplicity,
+    // we verify the events in sequence (since that is how the test is implemented).
+    for (size_t i = 0; i < nSamples; ++i) {
+        verifyArgsEqualToEvent(argsMoves[i], singleSampledMotionEvents[i]);
+        verifyFinishedSignal(*mPublisher, publishedSequenceNumbers.front(), publishTimes[i]);
+        publishedSequenceNumbers.pop();
+    }
+}
+
 void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeBatchedMotionMove(
         nsecs_t downTime) {
     uint32_t seq = mSeq++;
@@ -814,4 +933,8 @@
     ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
 }
 
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishAndConsumeSinglePointer) {
+    publishAndConsumeSinglePointerMultipleSamples(3);
+}
+
 } // namespace android
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 4a04467..ecf98c6 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -105,6 +105,7 @@
         "skia/filters/LinearEffect.cpp",
         "skia/filters/MouriMap.cpp",
         "skia/filters/StretchShaderFactory.cpp",
+        "skia/filters/EdgeExtensionShaderFactory.cpp",
     ],
 }
 
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index 8ac0af4..859ae8b 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -31,6 +31,7 @@
 #include <ui/ShadowSettings.h>
 #include <ui/StretchEffect.h>
 #include <ui/Transform.h>
+#include "ui/EdgeExtensionEffect.h"
 
 #include <iosfwd>
 
@@ -134,6 +135,7 @@
     mat4 blurRegionTransform = mat4();
 
     StretchEffect stretchEffect;
+    EdgeExtensionEffect edgeExtensionEffect;
 
     // Name associated with the layer for debugging purposes.
     std::string name;
@@ -183,7 +185,9 @@
             lhs.skipContentDraw == rhs.skipContentDraw && lhs.shadow == rhs.shadow &&
             lhs.backgroundBlurRadius == rhs.backgroundBlurRadius &&
             lhs.blurRegionTransform == rhs.blurRegionTransform &&
-            lhs.stretchEffect == rhs.stretchEffect && lhs.whitePointNits == rhs.whitePointNits;
+            lhs.stretchEffect == rhs.stretchEffect &&
+            lhs.edgeExtensionEffect == rhs.edgeExtensionEffect &&
+            lhs.whitePointNits == rhs.whitePointNits;
 }
 
 static inline void PrintTo(const Buffer& settings, ::std::ostream* os) {
@@ -254,6 +258,10 @@
     *os << "\n}";
 }
 
+static inline void PrintTo(const EdgeExtensionEffect& effect, ::std::ostream* os) {
+    *os << effect;
+}
+
 static inline void PrintTo(const LayerSettings& settings, ::std::ostream* os) {
     *os << "LayerSettings for '" << settings.name.c_str() << "' {";
     *os << "\n    .geometry = ";
@@ -285,6 +293,10 @@
         *os << "\n    .stretchEffect = ";
         PrintTo(settings.stretchEffect, os);
     }
+
+    if (settings.edgeExtensionEffect.hasEffect()) {
+        *os << "\n    .edgeExtensionEffect = " << settings.edgeExtensionEffect;
+    }
     *os << "\n    .whitePointNits = " << settings.whitePointNits;
     *os << "\n}";
 }
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index e62640e..a609f2d 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -507,16 +507,24 @@
         const RuntimeEffectShaderParameters& parameters) {
     // The given surface will be stretched by HWUI via matrix transformation
     // which gets similar results for most surfaces
-    // Determine later on if we need to leverage the stertch shader within
+    // Determine later on if we need to leverage the stretch shader within
     // surface flinger
     const auto& stretchEffect = parameters.layer.stretchEffect;
     const auto& targetBuffer = parameters.layer.source.buffer.buffer;
+    const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
+
     auto shader = parameters.shader;
-    if (stretchEffect.hasEffect()) {
-        const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
-        if (graphicBuffer && parameters.shader) {
+    if (graphicBuffer && parameters.shader) {
+        if (stretchEffect.hasEffect()) {
             shader = mStretchShaderFactory.createSkShader(shader, stretchEffect);
         }
+        // The given surface requires to be filled outside of its buffer bounds if the edge
+        // extension is required
+        const auto& edgeExtensionEffect = parameters.layer.edgeExtensionEffect;
+        if (edgeExtensionEffect.hasEffect()) {
+            shader = mEdgeExtensionShaderFactory.createSkShader(shader, parameters.layer,
+                                                                parameters.imageBounds);
+        }
     }
 
     if (parameters.requiresLinearEffect) {
@@ -525,21 +533,26 @@
                           static_cast<ui::PixelFormat>(targetBuffer->getPixelFormat()))
                 : std::nullopt;
 
-        if (parameters.display.tonemapStrategy == DisplaySettings::TonemapStrategy::Local) {
-            // TODO: Handle color matrix transforms in linear space.
-            SkImage* image = parameters.shader->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr);
-            if (image) {
-                static MouriMap kMapper;
-                const float ratio = getHdrRenderType(parameters.layer.sourceDataspace, format) ==
-                                HdrRenderType::GENERIC_HDR
-                        ? 1.0f
-                        : parameters.layerDimmingRatio;
-                return kMapper.mouriMap(getActiveContext(), parameters.shader, ratio);
-            }
+        const auto hdrType = getHdrRenderType(parameters.layer.sourceDataspace, format,
+                                              parameters.layerDimmingRatio);
+
+        const auto usingLocalTonemap =
+                parameters.display.tonemapStrategy == DisplaySettings::TonemapStrategy::Local &&
+                hdrType != HdrRenderType::SDR &&
+                shader->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr);
+
+        if (usingLocalTonemap) {
+            static MouriMap kMapper;
+            const float ratio =
+                    hdrType == HdrRenderType::GENERIC_HDR ? 1.0f : parameters.layerDimmingRatio;
+            shader = kMapper.mouriMap(getActiveContext(), shader, ratio);
         }
 
+        // disable tonemapping if we already locally tonemapped
+        auto inputDataspace =
+                usingLocalTonemap ? parameters.outputDataSpace : parameters.layer.sourceDataspace;
         auto effect =
-                shaders::LinearEffect{.inputDataspace = parameters.layer.sourceDataspace,
+                shaders::LinearEffect{.inputDataspace = inputDataspace,
                                       .outputDataspace = parameters.outputDataSpace,
                                       .undoPremultipliedAlpha = parameters.undoPremultipliedAlpha,
                                       .fakeOutputDataspace = parameters.fakeOutputDataspace};
@@ -555,20 +568,20 @@
 
         mat4 colorTransform = parameters.layer.colorTransform;
 
-        colorTransform *=
-                mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio,
-                                 parameters.layerDimmingRatio, 1.f));
+        if (!usingLocalTonemap) {
+            colorTransform *=
+                    mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio,
+                                     parameters.layerDimmingRatio, 1.f));
+        }
 
-        const auto targetBuffer = parameters.layer.source.buffer.buffer;
-        const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
         const auto hardwareBuffer = graphicBuffer ? graphicBuffer->toAHardwareBuffer() : nullptr;
-        return createLinearEffectShader(parameters.shader, effect, runtimeEffect,
-                                        std::move(colorTransform), parameters.display.maxLuminance,
+        return createLinearEffectShader(shader, effect, runtimeEffect, std::move(colorTransform),
+                                        parameters.display.maxLuminance,
                                         parameters.display.currentLuminanceNits,
                                         parameters.layer.source.buffer.maxLuminanceNits,
                                         hardwareBuffer, parameters.display.renderIntent);
     }
-    return parameters.shader;
+    return shader;
 }
 
 void SkiaRenderEngine::initCanvas(SkCanvas* canvas, const DisplaySettings& display) {
@@ -1032,18 +1045,20 @@
                                                            toSkColorSpace(layerDataspace)));
             }
 
-            paint.setShader(createRuntimeEffectShader(
-                    RuntimeEffectShaderParameters{.shader = shader,
-                                                  .layer = layer,
-                                                  .display = display,
-                                                  .undoPremultipliedAlpha = !item.isOpaque &&
-                                                          item.usePremultipliedAlpha,
-                                                  .requiresLinearEffect = requiresLinearEffect,
-                                                  .layerDimmingRatio = dimInLinearSpace
-                                                          ? layerDimmingRatio
-                                                          : 1.f,
-                                                  .outputDataSpace = display.outputDataspace,
-                                                  .fakeOutputDataspace = fakeDataspace}));
+            SkRect imageBounds;
+            matrix.mapRect(&imageBounds, SkRect::Make(image->bounds()));
+
+            paint.setShader(createRuntimeEffectShader(RuntimeEffectShaderParameters{
+                    .shader = shader,
+                    .layer = layer,
+                    .display = display,
+                    .undoPremultipliedAlpha = !item.isOpaque && item.usePremultipliedAlpha,
+                    .requiresLinearEffect = requiresLinearEffect,
+                    .layerDimmingRatio = dimInLinearSpace ? layerDimmingRatio : 1.f,
+                    .outputDataSpace = display.outputDataspace,
+                    .fakeOutputDataspace = fakeDataspace,
+                    .imageBounds = imageBounds,
+            }));
 
             // Turn on dithering when dimming beyond this (arbitrary) threshold...
             static constexpr float kDimmingThreshold = 0.9f;
@@ -1111,7 +1126,8 @@
                                                   .requiresLinearEffect = requiresLinearEffect,
                                                   .layerDimmingRatio = layerDimmingRatio,
                                                   .outputDataSpace = display.outputDataspace,
-                                                  .fakeOutputDataspace = fakeDataspace}));
+                                                  .fakeOutputDataspace = fakeDataspace,
+                                                  .imageBounds = SkRect::MakeEmpty()}));
         }
 
         if (layer.disableBlending) {
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index c8f9241..224a1ca 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -38,6 +38,7 @@
 #include "compat/SkiaGpuContext.h"
 #include "debug/SkiaCapture.h"
 #include "filters/BlurFilter.h"
+#include "filters/EdgeExtensionShaderFactory.h"
 #include "filters/LinearEffect.h"
 #include "filters/StretchShaderFactory.h"
 
@@ -156,6 +157,7 @@
         float layerDimmingRatio;
         const ui::Dataspace outputDataSpace;
         const ui::Dataspace fakeOutputDataspace;
+        const SkRect& imageBounds;
     };
     sk_sp<SkShader> createRuntimeEffectShader(const RuntimeEffectShaderParameters&);
 
@@ -175,6 +177,7 @@
     AutoBackendTexture::CleanupManager mTextureCleanupMgr GUARDED_BY(mRenderingMutex);
 
     StretchShaderFactory mStretchShaderFactory;
+    EdgeExtensionShaderFactory mEdgeExtensionShaderFactory;
 
     sp<Fence> mLastDrawFence;
     BlurFilter* mBlurFilter = nullptr;
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index bd50107..a1f917d 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -29,7 +29,6 @@
 #include <GrDirectContext.h>
 #include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
 #include <include/gpu/ganesh/vk/GrVkDirectContext.h>
-#include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
 
 #include <android-base/stringprintf.h>
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h
index 0a2f9b2..d2bb3d5 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.h
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.h
@@ -17,8 +17,6 @@
 #ifndef SF_SKIAVKRENDERENGINE_H_
 #define SF_SKIAVKRENDERENGINE_H_
 
-#include <vk/GrVkBackendContext.h>
-
 #include "SkiaRenderEngine.h"
 #include "VulkanInterface.h"
 #include "compat/SkiaGpuContext.h"
diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp
index 5e756b0..37b69f6 100644
--- a/libs/renderengine/skia/VulkanInterface.cpp
+++ b/libs/renderengine/skia/VulkanInterface.cpp
@@ -32,21 +32,8 @@
 namespace renderengine {
 namespace skia {
 
-GrVkBackendContext VulkanInterface::getGaneshBackendContext() {
-    GrVkBackendContext backendContext;
-    backendContext.fInstance = mInstance;
-    backendContext.fPhysicalDevice = mPhysicalDevice;
-    backendContext.fDevice = mDevice;
-    backendContext.fQueue = mQueue;
-    backendContext.fGraphicsQueueIndex = mQueueIndex;
-    backendContext.fMaxAPIVersion = mApiVersion;
-    backendContext.fVkExtensions = &mGrExtensions;
-    backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2;
-    backendContext.fGetProc = mGrGetProc;
-    backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo;
-    backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived
-    backendContext.fDeviceLostProc = onVkDeviceFault;
-    return backendContext;
+VulkanBackendContext VulkanInterface::getGaneshBackendContext() {
+    return this->getGraphiteBackendContext();
 };
 
 VulkanBackendContext VulkanInterface::getGraphiteBackendContext() {
@@ -57,7 +44,7 @@
     backendContext.fQueue = mQueue;
     backendContext.fGraphicsQueueIndex = mQueueIndex;
     backendContext.fMaxAPIVersion = mApiVersion;
-    backendContext.fVkExtensions = &mGrExtensions;
+    backendContext.fVkExtensions = &mVulkanExtensions;
     backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2;
     backendContext.fGetProc = mGrGetProc;
     backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo;
@@ -429,11 +416,11 @@
         mDeviceExtensionNames.push_back(devExt.extensionName);
     }
 
-    mGrExtensions.init(sGetProc, instance, physicalDevice, enabledInstanceExtensionNames.size(),
-                       enabledInstanceExtensionNames.data(), enabledDeviceExtensionNames.size(),
-                       enabledDeviceExtensionNames.data());
+    mVulkanExtensions.init(sGetProc, instance, physicalDevice, enabledInstanceExtensionNames.size(),
+                           enabledInstanceExtensionNames.data(), enabledDeviceExtensionNames.size(),
+                           enabledDeviceExtensionNames.data());
 
-    if (!mGrExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
+    if (!mVulkanExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
         BAIL("Vulkan driver doesn't support external semaphore fd");
     }
 
@@ -458,7 +445,7 @@
         tailPnext = &mProtectedMemoryFeatures->pNext;
     }
 
-    if (mGrExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+    if (mVulkanExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
         mDeviceFaultFeatures = new VkPhysicalDeviceFaultFeaturesEXT;
         mDeviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
         mDeviceFaultFeatures->pNext = nullptr;
@@ -484,7 +471,7 @@
             queuePriority,
     };
 
-    if (mGrExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
+    if (mVulkanExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
         queueNextPtr = &queuePriorityCreateInfo;
     }
 
@@ -606,7 +593,7 @@
     mQueue = VK_NULL_HANDLE;          // Implicitly destroyed by destroying mDevice.
     mQueueIndex = 0;
     mApiVersion = 0;
-    mGrExtensions = skgpu::VulkanExtensions();
+    mVulkanExtensions = skgpu::VulkanExtensions();
     mGrGetProc = nullptr;
     mIsProtected = false;
     mIsRealtimePriority = false;
diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h
index f20b002..d0fe4d1 100644
--- a/libs/renderengine/skia/VulkanInterface.h
+++ b/libs/renderengine/skia/VulkanInterface.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include <include/gpu/vk/GrVkBackendContext.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
 #include <include/gpu/vk/VulkanExtensions.h>
 #include <include/gpu/vk/VulkanTypes.h>
 
@@ -24,10 +24,6 @@
 
 using namespace skgpu;
 
-namespace skgpu {
-struct VulkanBackendContext;
-} // namespace skgpu
-
 namespace android {
 namespace renderengine {
 namespace skia {
@@ -48,7 +44,8 @@
     bool takeOwnership();
     void teardown();
 
-    GrVkBackendContext getGaneshBackendContext();
+    // TODO(b/309785258) Combine these into one now that they are the same implementation.
+    VulkanBackendContext getGaneshBackendContext();
     VulkanBackendContext getGraphiteBackendContext();
     VkSemaphore createExportableSemaphore();
     VkSemaphore importSemaphoreFromSyncFd(int syncFd);
@@ -86,7 +83,7 @@
     VkQueue mQueue = VK_NULL_HANDLE;
     int mQueueIndex = 0;
     uint32_t mApiVersion = 0;
-    skgpu::VulkanExtensions mGrExtensions;
+    skgpu::VulkanExtensions mVulkanExtensions;
     VkPhysicalDeviceFeatures2* mPhysicalDeviceFeatures2 = nullptr;
     VkPhysicalDeviceSamplerYcbcrConversionFeatures* mSamplerYcbcrConversionFeatures = nullptr;
     VkPhysicalDeviceProtectedMemoryFeatures* mProtectedMemoryFeatures = nullptr;
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.cpp b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
index b2eae00..b121fe8 100644
--- a/libs/renderengine/skia/compat/GaneshGpuContext.cpp
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
@@ -25,7 +25,7 @@
 #include <include/gpu/ganesh/gl/GrGLDirectContext.h>
 #include <include/gpu/ganesh/vk/GrVkDirectContext.h>
 #include <include/gpu/gl/GrGLInterface.h>
-#include <include/gpu/vk/GrVkBackendContext.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
 
 #include "../AutoBackendTexture.h"
 #include "GaneshBackendTexture.h"
@@ -56,10 +56,10 @@
 }
 
 std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeVulkan_Ganesh(
-        const GrVkBackendContext& grVkBackendContext,
+        const skgpu::VulkanBackendContext& vkBackendContext,
         GrContextOptions::PersistentCache& skSLCacheMonitor) {
     return std::make_unique<GaneshGpuContext>(
-            GrDirectContexts::MakeVulkan(grVkBackendContext, ganeshOptions(skSLCacheMonitor)));
+            GrDirectContexts::MakeVulkan(vkBackendContext, ganeshOptions(skSLCacheMonitor)));
 }
 
 GaneshGpuContext::GaneshGpuContext(sk_sp<GrDirectContext> grContext) : mGrContext(grContext) {
diff --git a/libs/renderengine/skia/compat/SkiaGpuContext.h b/libs/renderengine/skia/compat/SkiaGpuContext.h
index 282dfe7..9fa6fb8 100644
--- a/libs/renderengine/skia/compat/SkiaGpuContext.h
+++ b/libs/renderengine/skia/compat/SkiaGpuContext.h
@@ -23,7 +23,6 @@
 #include <include/gpu/GrDirectContext.h>
 #include <include/gpu/gl/GrGLInterface.h>
 #include <include/gpu/graphite/Context.h>
-#include <include/gpu/vk/GrVkBackendContext.h>
 #include "include/gpu/vk/VulkanBackendContext.h"
 
 #include "SkiaBackendTexture.h"
@@ -52,10 +51,10 @@
             GrContextOptions::PersistentCache& skSLCacheMonitor);
 
     /**
-     * grVkBackendContext must remain valid until after SkiaGpuContext is destroyed.
+     * vkBackendContext must remain valid until after SkiaGpuContext is destroyed.
      */
     static std::unique_ptr<SkiaGpuContext> MakeVulkan_Ganesh(
-            const GrVkBackendContext& grVkBackendContext,
+            const skgpu::VulkanBackendContext& vkBackendContext,
             GrContextOptions::PersistentCache& skSLCacheMonitor);
 
     // TODO: b/293371537 - Need shader / pipeline monitoring support in Graphite.
diff --git a/libs/renderengine/skia/filters/EdgeExtensionShaderFactory.cpp b/libs/renderengine/skia/filters/EdgeExtensionShaderFactory.cpp
new file mode 100644
index 0000000..1dbcc29
--- /dev/null
+++ b/libs/renderengine/skia/filters/EdgeExtensionShaderFactory.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EdgeExtensionShaderFactory.h"
+#include <SkPoint.h>
+#include <SkRuntimeEffect.h>
+#include <SkStream.h>
+#include <SkString.h>
+#include "log/log_main.h"
+
+namespace android::renderengine::skia {
+
+static const SkString edgeShader = SkString(R"(
+    uniform shader uContentTexture;
+    uniform vec2 uImgSize;
+
+    // TODO(b/214232209) oobTolerance is temporary and will be removed when the scrollbar will be
+    // hidden during the animation
+    const float oobTolerance = 15;
+    const int blurRadius = 3;
+    const float blurArea = float((2 * blurRadius + 1) * (2 * blurRadius + 1));
+
+    vec4 boxBlur(vec2 p) {
+        vec4 sumColors = vec4(0);
+
+        for (int i = -blurRadius; i <= blurRadius; i++) {
+            for (int j = -blurRadius; j <= blurRadius; j++) {
+                sumColors += uContentTexture.eval(p + vec2(i, j));
+            }
+        }
+        return sumColors / blurArea;
+    }
+
+    vec4 main(vec2 coord) {
+        vec2 nearestTexturePoint = clamp(coord, vec2(0, 0), uImgSize);
+        if (coord == nearestTexturePoint) {
+            return uContentTexture.eval(coord);
+        } else {
+            vec2 samplePoint = nearestTexturePoint + oobTolerance * normalize(
+                                    nearestTexturePoint - coord);
+            return boxBlur(samplePoint);
+        }
+    }
+)");
+
+sk_sp<SkShader> EdgeExtensionShaderFactory::createSkShader(const sk_sp<SkShader>& inputShader,
+                                                           const LayerSettings& layer,
+                                                           const SkRect& imageBounds) {
+    if (mBuilder == nullptr) {
+        const static SkRuntimeEffect::Result instance = SkRuntimeEffect::MakeForShader(edgeShader);
+        if (!instance.errorText.isEmpty()) {
+            ALOGE("EdgeExtensionShaderFactory terminated with an error: %s",
+                  instance.errorText.c_str());
+            return nullptr;
+        }
+        mBuilder = std::make_unique<SkRuntimeShaderBuilder>(instance.effect);
+    }
+    mBuilder->child("uContentTexture") = inputShader;
+    if (imageBounds.isEmpty()) {
+        mBuilder->uniform("uImgSize") = SkPoint{layer.geometry.boundaries.getWidth(),
+                                                layer.geometry.boundaries.getHeight()};
+    } else {
+        mBuilder->uniform("uImgSize") = SkPoint{imageBounds.width(), imageBounds.height()};
+    }
+    return mBuilder->makeShader();
+}
+} // namespace android::renderengine::skia
\ No newline at end of file
diff --git a/libs/renderengine/skia/filters/EdgeExtensionShaderFactory.h b/libs/renderengine/skia/filters/EdgeExtensionShaderFactory.h
new file mode 100644
index 0000000..b0a8a93
--- /dev/null
+++ b/libs/renderengine/skia/filters/EdgeExtensionShaderFactory.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <SkImage.h>
+#include <SkRect.h>
+#include <SkRuntimeEffect.h>
+#include <SkShader.h>
+#include <renderengine/LayerSettings.h>
+#include <ui/EdgeExtensionEffect.h>
+
+namespace android::renderengine::skia {
+
+/**
+ * This shader is designed to prolong the texture of a surface whose bounds have been extended over
+ * the size of the texture. This shader is similar to the default clamp, but adds a blur effect and
+ * samples from close to the edge (compared to on the edge) to avoid weird artifacts when elements
+ * (in particular, scrollbars) touch the edge.
+ */
+class EdgeExtensionShaderFactory {
+public:
+    sk_sp<SkShader> createSkShader(const sk_sp<SkShader>& inputShader, const LayerSettings& layer,
+                                   const SkRect& imageBounds);
+
+private:
+    std::unique_ptr<SkRuntimeShaderBuilder> mBuilder;
+};
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/filters/MouriMap.cpp b/libs/renderengine/skia/filters/MouriMap.cpp
index 7d8b8a5..b458939 100644
--- a/libs/renderengine/skia/filters/MouriMap.cpp
+++ b/libs/renderengine/skia/filters/MouriMap.cpp
@@ -35,7 +35,7 @@
         float maximum = 0.0;
         for (int y = 0; y < 16; y++) {
             for (int x = 0; x < 16; x++) {
-                float3 linear = toLinearSrgb(bitmap.eval(xy * 16 + vec2(x, y)).rgb) * hdrSdrRatio;
+                float3 linear = toLinearSrgb(bitmap.eval((xy - 0.5) * 16 + 0.5 + vec2(x, y)).rgb) * hdrSdrRatio;
                 float maxRGB = max(linear.r, max(linear.g, linear.b));
                 maximum = max(maximum, log2(max(maxRGB, 1.0)));
             }
@@ -49,7 +49,7 @@
         float maximum = 0.0;
         for (int y = 0; y < 8; y++) {
             for (int x = 0; x < 8; x++) {
-                maximum = max(maximum, bitmap.eval(xy * 8 + vec2(x, y)).r);
+                maximum = max(maximum, bitmap.eval((xy - 0.5) * 8 + 0.5 + vec2(x, y)).r);
             }
         }
         return float4(float3(maximum), 1.0);
@@ -84,13 +84,13 @@
         float3 linear = toLinearSrgb(rgba.rgb) * hdrSdrRatio;
 
         if (localMax <= 1.0) {
-            return float4(fromLinearSrgb(linear), 1.0);
+            return float4(fromLinearSrgb(linear), rgba.a);
         }
 
         float maxRGB = max(linear.r, max(linear.g, linear.b));
         localMax = max(localMax, maxRGB);
         float gain = (1 + maxRGB / (localMax * localMax)) / (1 + maxRGB);
-        return float4(fromLinearSrgb(linear * gain), 1.0);
+        return float4(fromLinearSrgb(linear * gain), rgba.a);
     }
 )");
 
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index a8a9823..b5cc65f 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -34,9 +34,12 @@
 #include <ui/ColorSpace.h>
 #include <ui/PixelFormat.h>
 
+#include <algorithm>
 #include <chrono>
 #include <condition_variable>
+#include <filesystem>
 #include <fstream>
+#include <system_error>
 
 #include "../skia/SkiaGLRenderEngine.h"
 #include "../skia/SkiaVkRenderEngine.h"
@@ -259,22 +262,51 @@
 
     ~RenderEngineTest() {
         if (WRITE_BUFFER_TO_FILE_ON_FAILURE && ::testing::Test::HasFailure()) {
-            writeBufferToFile("/data/texture_out_");
+            writeBufferToFile("/data/local/tmp/RenderEngineTest/");
         }
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
         ALOGI("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
     }
 
-    void writeBufferToFile(const char* basename) {
-        std::string filename(basename);
-        filename.append(::testing::UnitTest::GetInstance()->current_test_info()->name());
-        filename.append(".ppm");
-        std::ofstream file(filename.c_str(), std::ios::binary);
+    // If called during e.g.
+    // `PerRenderEngineType/RenderEngineTest#drawLayers_fillBufferCheckersRotate90_colorSource/0`
+    // with a directory of `/data/local/tmp/RenderEngineTest`, then mBuffer will be dumped to
+    // `/data/local/tmp/RenderEngineTest/drawLayers_fillBufferCheckersRotate90_colorSource-0.ppm`
+    //
+    // Note: if `directory` does not exist, then its full path will be recursively created with 777
+    // permissions. If `directory` already exists but does not grant the executing user write
+    // permissions, then saving the buffer will fail.
+    //
+    // Since this is test-only code, no security considerations are made.
+    void writeBufferToFile(const filesystem::path& directory) {
+        const auto currentTestInfo = ::testing::UnitTest::GetInstance()->current_test_info();
+        LOG_ALWAYS_FATAL_IF(!currentTestInfo,
+                            "writeBufferToFile must be called during execution of a test");
+
+        std::string fileName(currentTestInfo->name());
+        // Test names may include the RenderEngine variant separated by '/', which would separate
+        // the file name into a subdirectory if not corrected.
+        std::replace(fileName.begin(), fileName.end(), '/', '-');
+        fileName.append(".ppm");
+
+        std::error_code err;
+        filesystem::create_directories(directory, err);
+        if (err.value()) {
+            ALOGE("Unable to create directory %s for writing %s (%d: %s)", directory.c_str(),
+                  fileName.c_str(), err.value(), err.message().c_str());
+            return;
+        }
+
+        // Append operator ("/") ensures exactly one "/" directly before the argument.
+        const filesystem::path filePath = directory / fileName;
+        std::ofstream file(filePath.c_str(), std::ios::binary);
         if (!file.is_open()) {
-            ALOGE("Unable to open file: %s", filename.c_str());
-            ALOGE("You may need to do: \"adb shell setenforce 0\" to enable "
-                  "surfaceflinger to write debug images");
+            ALOGE("Unable to open file: %s", filePath.c_str());
+            ALOGE("You may need to do: \"adb shell setenforce 0\" to enable surfaceflinger to "
+                  "write debug images, or the %s directory might not give the executing user write "
+                  "permission",
+                  directory.c_str());
             return;
         }
 
@@ -304,6 +336,7 @@
             }
         }
         file.write(reinterpret_cast<char*>(outBuffer.data()), outBuffer.size());
+        ALOGI("Image of incorrect output written to %s", filePath.c_str());
         mBuffer->getBuffer()->unlock();
     }
 
@@ -3147,6 +3180,214 @@
     expectBufferColor(Rect(0, 0, 1, 1), 0,  70, 0, 255);
 }
 
+TEST_P(RenderEngineTest, localTonemap_preservesFullscreenSdr) {
+    if (!GetParam()->apiSupported()) {
+        GTEST_SKIP();
+    }
+
+    initializeRenderEngine();
+
+    mBuffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::make(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1,
+                                                             GRALLOC_USAGE_SW_READ_OFTEN |
+                                                                     GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                                     GRALLOC_USAGE_HW_RENDER |
+                                                                     GRALLOC_USAGE_HW_TEXTURE,
+                                                             "output"),
+                                     *mRE,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+    ASSERT_EQ(0, mBuffer->getBuffer()->initCheck());
+
+    const auto whiteBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(51, 51, 51, 255));
+
+    const auto rect = Rect(0, 0, 1, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .outputDataspace = ui::Dataspace::SRGB,
+            .targetLuminanceNits = 40,
+            .tonemapStrategy = renderengine::DisplaySettings::TonemapStrategy::Local,
+    };
+
+    const renderengine::LayerSettings whiteLayer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = whiteBuffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+            .sourceDataspace = ui::Dataspace::V0_SCRGB_LINEAR,
+            .whitePointNits = 200,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{whiteLayer};
+    invokeDraw(display, layers);
+
+    expectBufferColor(Rect(0, 0, 1, 1), 255, 255, 255, 255);
+}
+
+TEST_P(RenderEngineTest, localTonemap_preservesFarawaySdrRegions) {
+    if (!GetParam()->apiSupported()) {
+        GTEST_SKIP();
+    }
+
+    initializeRenderEngine();
+
+    const auto blockWidth = 256;
+    const auto width = blockWidth * 4;
+
+    const auto buffer = allocateSourceBuffer(width, 1);
+
+    mBuffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::make(width, 1, HAL_PIXEL_FORMAT_RGBA_8888,
+                                                             1,
+                                                             GRALLOC_USAGE_SW_READ_OFTEN |
+                                                                     GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                                     GRALLOC_USAGE_HW_RENDER |
+                                                                     GRALLOC_USAGE_HW_TEXTURE,
+                                                             "output"),
+                                     *mRE,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+
+    {
+        uint8_t* pixels;
+        buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                  reinterpret_cast<void**>(&pixels));
+        uint8_t* dst = pixels;
+        for (uint32_t i = 0; i < width; i++) {
+            uint8_t value = 0;
+            if (i < blockWidth) {
+                value = 51;
+            } else if (i >= blockWidth * 3) {
+                value = 255;
+            }
+            dst[0] = value;
+            dst[1] = value;
+            dst[2] = value;
+            dst[3] = 255;
+            dst += 4;
+        }
+        buffer->getBuffer()->unlock();
+    }
+
+    const auto rect = Rect(0, 0, width, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .outputDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .targetLuminanceNits = 40,
+            .tonemapStrategy = renderengine::DisplaySettings::TonemapStrategy::Local,
+    };
+
+    const renderengine::LayerSettings whiteLayer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = buffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+            .sourceDataspace = ui::Dataspace::V0_SCRGB_LINEAR,
+            .whitePointNits = 200,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{whiteLayer};
+    invokeDraw(display, layers);
+
+    // SDR regions are boosted to preserve SDR detail.
+    expectBufferColor(Rect(0, 0, blockWidth, 1), 255, 255, 255, 255);
+    expectBufferColor(Rect(blockWidth, 0, blockWidth * 2, 1), 0, 0, 0, 255);
+    expectBufferColor(Rect(blockWidth * 2, 0, blockWidth * 3, 1), 0, 0, 0, 255);
+    expectBufferColor(Rect(blockWidth * 3, 0, blockWidth * 4, 1), 255, 255, 255, 255);
+}
+
+TEST_P(RenderEngineTest, localTonemap_tonemapsNearbySdrRegions) {
+    if (!GetParam()->apiSupported()) {
+        GTEST_SKIP();
+    }
+
+    initializeRenderEngine();
+
+    const auto blockWidth = 2;
+    const auto width = blockWidth * 2;
+
+    mBuffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::make(width, 1, HAL_PIXEL_FORMAT_RGBA_8888,
+                                                             1,
+                                                             GRALLOC_USAGE_SW_READ_OFTEN |
+                                                                     GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                                     GRALLOC_USAGE_HW_RENDER |
+                                                                     GRALLOC_USAGE_HW_TEXTURE,
+                                                             "output"),
+                                     *mRE,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+
+    const auto buffer = allocateSourceBuffer(width, 1);
+
+    {
+        uint8_t* pixels;
+        buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                  reinterpret_cast<void**>(&pixels));
+        uint8_t* dst = pixels;
+        for (uint32_t i = 0; i < width; i++) {
+            uint8_t value = 0;
+            if (i < blockWidth) {
+                value = 51;
+            } else if (i >= blockWidth) {
+                value = 255;
+            }
+            dst[0] = value;
+            dst[1] = value;
+            dst[2] = value;
+            dst[3] = 255;
+            dst += 4;
+        }
+        buffer->getBuffer()->unlock();
+    }
+
+    const auto rect = Rect(0, 0, width, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .outputDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .targetLuminanceNits = 40,
+            .tonemapStrategy = renderengine::DisplaySettings::TonemapStrategy::Local,
+    };
+
+    const renderengine::LayerSettings whiteLayer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = buffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+            .sourceDataspace = ui::Dataspace::V0_SCRGB_LINEAR,
+            .whitePointNits = 200,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{whiteLayer};
+    invokeDraw(display, layers);
+
+    // SDR regions remain "dimmed", but preserve detail with a roll-off curve.
+    expectBufferColor(Rect(0, 0, blockWidth, 1), 132, 132, 132, 255, 2);
+    // HDR regions are not dimmed.
+    expectBufferColor(Rect(blockWidth, 0, blockWidth * 2, 1), 255, 255, 255, 255);
+}
+
 TEST_P(RenderEngineTest, primeShaderCache) {
     // TODO: b/331447071 - Fix in Graphite and re-enable.
     if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) {
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index 7fa47b4..659666d 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -63,6 +63,8 @@
         "libhardware",
         "libpermission",
         "android.companion.virtual.virtualdevice_aidl-cpp",
+        "libaconfig_storage_read_api_cc",
+        "server_configurable_flags",
     ],
 
     static_libs: [
diff --git a/libs/sensor/SensorEventQueue.cpp b/libs/sensor/SensorEventQueue.cpp
index 4438d45..84852ea 100644
--- a/libs/sensor/SensorEventQueue.cpp
+++ b/libs/sensor/SensorEventQueue.cpp
@@ -15,31 +15,40 @@
  */
 
 #define LOG_TAG "Sensors"
-
-#include <sensor/SensorEventQueue.h>
-
-#include <algorithm>
-#include <sys/socket.h>
-
-#include <utils/RefBase.h>
-#include <utils/Looper.h>
-
-#include <sensor/Sensor.h>
-#include <sensor/BitTube.h>
-#include <sensor/ISensorEventConnection.h>
+#define ATRACE_TAG ATRACE_TAG_SYSTEM_SERVER
 
 #include <android/sensor.h>
+#include <com_android_hardware_libsensor_flags.h>
+#include <cutils/trace.h>
 #include <hardware/sensors-base.h>
+#include <sensor/BitTube.h>
+#include <sensor/ISensorEventConnection.h>
+#include <sensor/Sensor.h>
+#include <sensor/SensorEventQueue.h>
+#include <sensor/SensorManager.h>
+#include <sys/socket.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
+
+#include <algorithm>
+#include <cinttypes>
+#include <string>
 
 using std::min;
+namespace libsensor_flags = com::android::hardware::libsensor::flags;
 
 // ----------------------------------------------------------------------------
 namespace android {
 // ----------------------------------------------------------------------------
 
-SensorEventQueue::SensorEventQueue(const sp<ISensorEventConnection>& connection)
-    : mSensorEventConnection(connection), mRecBuffer(nullptr), mAvailable(0), mConsumed(0),
-      mNumAcksToSend(0) {
+SensorEventQueue::SensorEventQueue(const sp<ISensorEventConnection>& connection,
+                                   SensorManager& sensorManager)
+      : mSensorEventConnection(connection),
+        mRecBuffer(nullptr),
+        mSensorManager(sensorManager),
+        mAvailable(0),
+        mConsumed(0),
+        mNumAcksToSend(0) {
     mRecBuffer = new ASensorEvent[MAX_RECEIVE_BUFFER_EVENT_COUNT];
 }
 
@@ -65,8 +74,8 @@
 
 ssize_t SensorEventQueue::read(ASensorEvent* events, size_t numEvents) {
     if (mAvailable == 0) {
-        ssize_t err = BitTube::recvObjects(mSensorChannel,
-                mRecBuffer, MAX_RECEIVE_BUFFER_EVENT_COUNT);
+        ssize_t err =
+                BitTube::recvObjects(mSensorChannel, mRecBuffer, MAX_RECEIVE_BUFFER_EVENT_COUNT);
         if (err < 0) {
             return err;
         }
@@ -75,6 +84,20 @@
     }
     size_t count = min(numEvents, mAvailable);
     memcpy(events, mRecBuffer + mConsumed, count * sizeof(ASensorEvent));
+
+    if (CC_UNLIKELY(ATRACE_ENABLED()) &&
+        libsensor_flags::sensor_event_queue_report_sensor_usage_in_tracing()) {
+        for (size_t i = 0; i < count; i++) {
+            std::optional<std::string_view> sensorName =
+                    mSensorManager.getSensorNameByHandle(events->sensor);
+            if (sensorName.has_value()) {
+                char buffer[UINT8_MAX];
+                std::snprintf(buffer, sizeof(buffer), "Sensor event from %s",
+                              sensorName.value().data());
+                ATRACE_INSTANT_FOR_TRACK(LOG_TAG, buffer);
+            }
+        }
+    }
     mAvailable -= count;
     mConsumed += count;
     return static_cast<ssize_t>(count);
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 9411e20..3ca6f0f 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -38,6 +38,7 @@
 #include <sensor/SensorEventQueue.h>
 
 #include <com_android_hardware_libsensor_flags.h>
+namespace libsensor_flags = com::android::hardware::libsensor::flags;
 
 // ----------------------------------------------------------------------------
 namespace android {
@@ -78,6 +79,21 @@
     return DEVICE_ID_DEFAULT;
 }
 
+bool findSensorNameInList(int32_t handle, const Vector<Sensor>& sensorList,
+                          std::string* outString) {
+    for (auto& sensor : sensorList) {
+        if (sensor.getHandle() == handle) {
+            std::ostringstream oss;
+            oss << sensor.getStringType() << ":" << sensor.getName();
+            if (outString) {
+                *outString = std::move(oss.str());
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
 }  // namespace
 
 Mutex SensorManager::sLock;
@@ -355,6 +371,25 @@
     return nullptr;
 }
 
+std::optional<std::string_view> SensorManager::getSensorNameByHandle(int32_t handle) {
+    std::lock_guard<std::mutex> lock(mSensorHandleToNameMutex);
+    auto iterator = mSensorHandleToName.find(handle);
+    if (iterator != mSensorHandleToName.end()) {
+        return iterator->second;
+    }
+
+    std::string sensorName;
+    if (!findSensorNameInList(handle, mSensors, &sensorName) &&
+        !findSensorNameInList(handle, mDynamicSensors, &sensorName)) {
+        ALOGW("Cannot find sensor with handle %d", handle);
+        return std::nullopt;
+    }
+
+    mSensorHandleToName[handle] = std::move(sensorName);
+
+    return mSensorHandleToName[handle];
+}
+
 sp<SensorEventQueue> SensorManager::createEventQueue(
     String8 packageName, int mode, String16 attributionTag) {
     sp<SensorEventQueue> queue;
@@ -368,7 +403,7 @@
             ALOGE("createEventQueue: connection is NULL.");
             return nullptr;
         }
-        queue = new SensorEventQueue(connection);
+        queue = new SensorEventQueue(connection, *this);
         break;
     }
     return queue;
diff --git a/libs/sensor/include/sensor/SensorEventQueue.h b/libs/sensor/include/sensor/SensorEventQueue.h
index 8c3fde0..0bcaadc 100644
--- a/libs/sensor/include/sensor/SensorEventQueue.h
+++ b/libs/sensor/include/sensor/SensorEventQueue.h
@@ -42,6 +42,7 @@
 // ----------------------------------------------------------------------------
 
 class ISensorEventConnection;
+class SensorManager;
 class Sensor;
 class Looper;
 
@@ -65,7 +66,8 @@
     // Default sensor sample period
     static constexpr int32_t SENSOR_DELAY_NORMAL = 200000;
 
-    explicit SensorEventQueue(const sp<ISensorEventConnection>& connection);
+    explicit SensorEventQueue(const sp<ISensorEventConnection>& connection,
+                              SensorManager& sensorManager);
     virtual ~SensorEventQueue();
     virtual void onFirstRef();
 
@@ -107,6 +109,7 @@
     mutable Mutex mLock;
     mutable sp<Looper> mLooper;
     ASensorEvent* mRecBuffer;
+    SensorManager& mSensorManager;
     size_t mAvailable;
     size_t mConsumed;
     uint32_t mNumAcksToSend;
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index 49f050a..8d7237d 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -17,22 +17,20 @@
 #ifndef ANDROID_GUI_SENSOR_MANAGER_H
 #define ANDROID_GUI_SENSOR_MANAGER_H
 
-#include <map>
-#include <unordered_map>
-
-#include <stdint.h>
-#include <sys/types.h>
-
 #include <binder/IBinder.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
-
+#include <sensor/SensorEventQueue.h>
+#include <stdint.h>
+#include <sys/types.h>
 #include <utils/Errors.h>
+#include <utils/String8.h>
 #include <utils/StrongPointer.h>
 #include <utils/Vector.h>
-#include <utils/String8.h>
 
-#include <sensor/SensorEventQueue.h>
+#include <map>
+#include <string>
+#include <unordered_map>
 
 // ----------------------------------------------------------------------------
 // Concrete types for the NDK
@@ -66,6 +64,7 @@
     sp<SensorEventQueue> createEventQueue(
         String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16(""));
     bool isDataInjectionEnabled();
+    std::optional<std::string_view> getSensorNameByHandle(int32_t handle);
     bool isReplayDataInjectionEnabled();
     bool isHalBypassReplayDataInjectionEnabled();
     int createDirectChannel(size_t size, int channelType, const native_handle_t *channelData);
@@ -97,6 +96,9 @@
     const String16 mOpPackageName;
     const int mDeviceId;
     std::unordered_map<int, sp<ISensorEventConnection>> mDirectConnection;
+
+    std::mutex mSensorHandleToNameMutex;
+    std::unordered_map<int32_t, std::string> mSensorHandleToName;
     int32_t mDirectConnectionHandle;
 };
 
diff --git a/libs/ui/include/ui/EdgeExtensionEffect.h b/libs/ui/include/ui/EdgeExtensionEffect.h
new file mode 100644
index 0000000..02126b1
--- /dev/null
+++ b/libs/ui/include/ui/EdgeExtensionEffect.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/**
+ * Stores the edge that will be extended
+ */
+namespace android {
+
+enum CanonicalDirections { NONE = 0, LEFT = 1, RIGHT = 2, TOP = 4, BOTTOM = 8 };
+
+inline std::string to_string(CanonicalDirections direction) {
+    switch (direction) {
+        case LEFT:
+            return "LEFT";
+        case RIGHT:
+            return "RIGHT";
+        case TOP:
+            return "TOP";
+        case BOTTOM:
+            return "BOTTOM";
+        case NONE:
+            return "NONE";
+    }
+}
+
+struct EdgeExtensionEffect {
+    EdgeExtensionEffect(bool left, bool right, bool top, bool bottom) {
+        extensionEdges = NONE;
+        if (left) {
+            extensionEdges |= LEFT;
+        }
+        if (right) {
+            extensionEdges |= RIGHT;
+        }
+        if (top) {
+            extensionEdges |= TOP;
+        }
+        if (bottom) {
+            extensionEdges |= BOTTOM;
+        }
+    }
+
+    EdgeExtensionEffect() { EdgeExtensionEffect(false, false, false, false); }
+
+    bool extendsEdge(CanonicalDirections edge) const { return extensionEdges & edge; }
+
+    bool hasEffect() const { return extensionEdges != NONE; };
+
+    void reset() { extensionEdges = NONE; }
+
+    bool operator==(const EdgeExtensionEffect& other) const {
+        return extensionEdges == other.extensionEdges;
+    }
+
+    bool operator!=(const EdgeExtensionEffect& other) const { return !(*this == other); }
+
+private:
+    int extensionEdges;
+};
+
+inline std::string to_string(const EdgeExtensionEffect& effect) {
+    std::string s = "EdgeExtensionEffect={edges=[";
+    if (effect.hasEffect()) {
+        for (CanonicalDirections edge : {LEFT, RIGHT, TOP, BOTTOM}) {
+            if (effect.extendsEdge(edge)) {
+                s += to_string(edge) + ", ";
+            }
+        }
+    } else {
+        s += to_string(NONE);
+    }
+    s += "]}";
+    return s;
+}
+
+inline std::ostream& operator<<(std::ostream& os, const EdgeExtensionEffect effect) {
+    os << to_string(effect);
+    return os;
+}
+} // namespace android
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index a76271d..23c95e5 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2114,19 +2114,16 @@
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
         ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=%s, displayId=%s, policyFlags=0x%x, "
               "action=%s, actionButton=0x%x, flags=0x%x, "
-              "metaState=0x%x, buttonState=0x%x,"
-              "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%" PRId64,
+              "metaState=0x%x, buttonState=0x%x, downTime=%" PRId64,
               prefix, entry.eventTime, entry.deviceId,
               inputEventSourceToString(entry.source).c_str(), entry.displayId.toString().c_str(),
               entry.policyFlags, MotionEvent::actionToString(entry.action).c_str(),
-              entry.actionButton, entry.flags, entry.metaState, entry.buttonState, entry.edgeFlags,
-              entry.xPrecision, entry.yPrecision, entry.downTime);
+              entry.actionButton, entry.flags, entry.metaState, entry.buttonState, entry.downTime);
 
         for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%s, "
                   "x=%f, y=%f, pressure=%f, size=%f, "
-                  "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
-                  "orientation=%f",
+                  "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f",
                   i, entry.pointerProperties[i].id,
                   ftl::enum_string(entry.pointerProperties[i].toolType).c_str(),
                   entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
@@ -2497,9 +2494,8 @@
         }
 
         if (newTouchedWindows.empty()) {
-            ALOGI("Dropping event because there is no touchable window at (%.1f, %.1f) on display "
-                  "%s.",
-                  x, y, displayId.toString().c_str());
+            LOG(INFO) << "Dropping event because there is no touchable window at (" << x << ", "
+                      << y << ") on display " << displayId << ": " << entry;
             outInjectionResult = InputEventInjectionResult::FAILED;
             return {};
         }
@@ -3831,6 +3827,10 @@
                 }
                 const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
                 status = publishMotionEvent(*connection, *dispatchEntry);
+                if (status == BAD_VALUE) {
+                    logDispatchStateLocked();
+                    LOG(FATAL) << "Publisher failed for " << motionEntry;
+                }
                 if (mTracer) {
                     ensureEventTraced(motionEntry);
                     mTracer->traceEventDispatch(*dispatchEntry, *motionEntry.traceTracker);
@@ -4549,13 +4549,12 @@
         ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=%s, "
               "displayId=%s, policyFlags=0x%x, "
               "action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, "
-              "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, "
-              "yCursorPosition=%f, downTime=%" PRId64,
+              "xCursorPosition=%f, yCursorPosition=%f, downTime=%" PRId64,
               args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(),
               args.displayId.toString().c_str(), args.policyFlags,
               MotionEvent::actionToString(args.action).c_str(), args.actionButton, args.flags,
-              args.metaState, args.buttonState, args.edgeFlags, args.xPrecision, args.yPrecision,
-              args.xCursorPosition, args.yCursorPosition, args.downTime);
+              args.metaState, args.buttonState, args.xCursorPosition, args.yCursorPosition,
+              args.downTime);
         for (uint32_t i = 0; i < args.getPointerCount(); i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%s, x=%f, y=%f, pressure=%f, size=%f, "
                   "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f",
@@ -6025,17 +6024,12 @@
                 dump += StringPrintf(INDENT3 "OutboundQueue: length=%zu\n",
                                      connection->outboundQueue.size());
                 dump += dumpQueue(connection->outboundQueue, currentTime);
-
-            } else {
-                dump += INDENT3 "OutboundQueue: <empty>\n";
             }
 
             if (!connection->waitQueue.empty()) {
                 dump += StringPrintf(INDENT3 "WaitQueue: length=%zu\n",
                                      connection->waitQueue.size());
                 dump += dumpQueue(connection->waitQueue, currentTime);
-            } else {
-                dump += INDENT3 "WaitQueue: <empty>\n";
             }
             std::string inputStateDump = streamableToString(connection->inputState);
             if (!inputStateDump.empty()) {
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index dfbe02f..4df23c5 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -647,7 +647,9 @@
     if (!state.mMotionMementos.empty()) {
         out << "mMotionMementos: ";
         for (const InputState::MotionMemento& memento : state.mMotionMementos) {
-            out << "{deviceId= " << memento.deviceId << ", hovering=" << memento.hovering << "}, ";
+            out << "{deviceId=" << memento.deviceId
+                << ", hovering=" << std::to_string(memento.hovering)
+                << ", downTime=" << memento.downTime << "}, ";
         }
     }
     return out;
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index e76b648..a052a4e 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -115,6 +115,7 @@
         "libinputreader_defaults",
     ],
     shared_libs: [
+        "android.companion.virtualdevice.flags-aconfig-cc-host",
         "libinputflinger_base",
     ],
     export_header_lib_headers: [
@@ -140,6 +141,7 @@
     shared_libs: [
         // This should consist only of dependencies from inputflinger. Other dependencies should be
         // in cc_defaults so that they are included in the tests.
+        "android.companion.virtualdevice.flags-aconfig-cc-host",
         "libinputflinger_base",
         "libjsoncpp",
     ],
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index fe70a51..9381580 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -33,6 +33,8 @@
 #include <sys/sysmacros.h>
 #include <unistd.h>
 
+#include <android_companion_virtualdevice_flags.h>
+
 #define LOG_TAG "EventHub"
 
 // #define LOG_NDEBUG 0
@@ -68,6 +70,8 @@
 
 namespace android {
 
+namespace vd_flags = android::companion::virtualdevice::flags;
+
 using namespace ftl::flag_operators;
 
 static const char* DEVICE_INPUT_PATH = "/dev/input";
@@ -513,10 +517,10 @@
 
 // --- RawAbsoluteAxisInfo ---
 
-std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info) {
-    if (info.valid) {
-        out << "min=" << info.minValue << ", max=" << info.maxValue << ", flat=" << info.flat
-            << ", fuzz=" << info.fuzz << ", resolution=" << info.resolution;
+std::ostream& operator<<(std::ostream& out, const std::optional<RawAbsoluteAxisInfo>& info) {
+    if (info) {
+        out << "min=" << info->minValue << ", max=" << info->maxValue << ", flat=" << info->flat
+            << ", fuzz=" << info->fuzz << ", resolution=" << info->resolution;
     } else {
         out << "unknown range";
     }
@@ -645,7 +649,6 @@
             continue;
         }
         auto& [axisInfo, value] = absState[axis];
-        axisInfo.valid = true;
         axisInfo.minValue = info.minimum;
         axisInfo.maxValue = info.maximum;
         axisInfo.flat = info.flat;
@@ -998,26 +1001,23 @@
     return *device->configuration;
 }
 
-status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                       RawAbsoluteAxisInfo* outAxisInfo) const {
-    outAxisInfo->clear();
+std::optional<RawAbsoluteAxisInfo> EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis) const {
     if (axis < 0 || axis > ABS_MAX) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     std::scoped_lock _l(mLock);
     const Device* device = getDeviceLocked(deviceId);
     if (device == nullptr) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     // We can read the RawAbsoluteAxisInfo even if the device is disabled and doesn't have a valid
     // fd, because the info is populated once when the device is first opened, and it doesn't change
     // throughout the device lifecycle.
     auto it = device->absState.find(axis);
     if (it == device->absState.end()) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
-    *outAxisInfo = it->second.info;
-    return OK;
+    return it->second.info;
 }
 
 bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
@@ -1130,22 +1130,20 @@
     return device->swState.test(sw) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
 }
 
-status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const {
-    *outValue = 0;
+std::optional<int32_t> EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const {
     if (axis < 0 || axis > ABS_MAX) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     std::scoped_lock _l(mLock);
     const Device* device = getDeviceLocked(deviceId);
     if (device == nullptr || !device->hasValidFd()) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     const auto it = device->absState.find(axis);
     if (it == device->absState.end()) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
-    *outValue = it->second.value;
-    return OK;
+    return it->second.value;
 }
 
 base::Result<std::vector<int32_t>> EventHub::getMtSlotValues(int32_t deviceId, int32_t axis,
@@ -2503,6 +2501,12 @@
         }
     }
 
+    // See if the device is a rotary encoder with a single scroll axis and nothing else.
+    if (vd_flags::virtual_rotary() && device->classes == ftl::Flags<InputDeviceClass>(0) &&
+        device->relBitmask.test(REL_WHEEL) && !device->relBitmask.test(REL_HWHEEL)) {
+        device->classes |= InputDeviceClass::ROTARY_ENCODER;
+    }
+
     // If the device isn't recognized as something we handle, don't monitor it.
     if (device->classes == ftl::Flags<InputDeviceClass>(0)) {
         ALOGV("Dropping device: id=%d, path='%s', name='%s'", deviceId, devicePath.c_str(),
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 7cf584d..c647558 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -21,6 +21,7 @@
 #include <filesystem>
 #include <functional>
 #include <map>
+#include <optional>
 #include <ostream>
 #include <string>
 #include <unordered_map>
@@ -70,18 +71,14 @@
 
 /* Describes an absolute axis. */
 struct RawAbsoluteAxisInfo {
-    bool valid{false}; // true if the information is valid, false otherwise
-
     int32_t minValue{};   // minimum value
     int32_t maxValue{};   // maximum value
     int32_t flat{};       // center flat position, eg. flat == 8 means center is between -8 and 8
     int32_t fuzz{};       // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
     int32_t resolution{}; // resolution in units per mm or radians per mm
-
-    inline void clear() { *this = RawAbsoluteAxisInfo(); }
 };
 
-std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info);
+std::ostream& operator<<(std::ostream& out, const std::optional<RawAbsoluteAxisInfo>& info);
 
 /*
  * Input device classes.
@@ -278,8 +275,8 @@
      */
     virtual std::optional<PropertyMap> getConfiguration(int32_t deviceId) const = 0;
 
-    virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                         RawAbsoluteAxisInfo* outAxisInfo) const = 0;
+    virtual std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                                   int axis) const = 0;
 
     virtual bool hasRelativeAxis(int32_t deviceId, int axis) const = 0;
 
@@ -339,8 +336,7 @@
     virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
     virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
     virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
-    virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                          int32_t* outValue) const = 0;
+    virtual std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const = 0;
     /* Query Multi-Touch slot values for an axis. Returns error or an 1 indexed array of size
      * (slotCount + 1). The value at the 0 index is set to queried axis. */
     virtual base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
@@ -511,8 +507,8 @@
 
     std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override final;
 
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override final;
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                           int axis) const override final;
 
     bool hasRelativeAxis(int32_t deviceId, int axis) const override final;
 
@@ -559,8 +555,8 @@
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override final;
     int32_t getKeyCodeForKeyLocation(int32_t deviceId,
                                      int32_t locationKeyCode) const override final;
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                  int32_t* outValue) const override final;
+    std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId,
+                                                int32_t axis) const override final;
     base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
                                                        size_t slotCount) const override final;
 
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 2a7e262..f2fdc37 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -305,19 +305,17 @@
     inline int32_t getDeviceControllerNumber() const {
         return mEventHub->getDeviceControllerNumber(mId);
     }
-    inline status_t getAbsoluteAxisInfo(int32_t code, RawAbsoluteAxisInfo* axisInfo) const {
-        if (const auto status = mEventHub->getAbsoluteAxisInfo(mId, code, axisInfo); status != OK) {
-            return status;
-        }
+    inline std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t code) const {
+        std::optional<RawAbsoluteAxisInfo> info = mEventHub->getAbsoluteAxisInfo(mId, code);
 
         // Validate axis info for InputDevice.
-        if (axisInfo->valid && axisInfo->minValue == axisInfo->maxValue) {
+        if (info && info->minValue == info->maxValue) {
             // Historically, we deem axes with the same min and max values as invalid to avoid
             // dividing by zero when scaling by max - min.
             // TODO(b/291772515): Perform axis info validation on a per-axis basis when it is used.
-            axisInfo->valid = false;
+            return std::nullopt;
         }
-        return OK;
+        return info;
     }
     inline bool hasRelativeAxis(int32_t code) const {
         return mEventHub->hasRelativeAxis(mId, code);
@@ -379,8 +377,8 @@
         return mEventHub->getKeyCodeForKeyLocation(mId, locationKeyCode);
     }
     inline int32_t getSwitchState(int32_t sw) const { return mEventHub->getSwitchState(mId, sw); }
-    inline status_t getAbsoluteAxisValue(int32_t code, int32_t* outValue) const {
-        return mEventHub->getAbsoluteAxisValue(mId, code, outValue);
+    inline std::optional<int32_t> getAbsoluteAxisValue(int32_t code) const {
+        return mEventHub->getAbsoluteAxisValue(mId, code);
     }
     inline base::Result<std::vector<int32_t>> getMtSlotValues(int32_t axis,
                                                               size_t slotCount) const {
@@ -432,9 +430,7 @@
     }
 
     inline bool hasAbsoluteAxis(int32_t code) const {
-        RawAbsoluteAxisInfo info;
-        mEventHub->getAbsoluteAxisInfo(mId, code, &info);
-        return info.valid;
+        return mEventHub->getAbsoluteAxisInfo(mId, code).has_value();
     }
     inline bool isKeyPressed(int32_t scanCode) const {
         return mEventHub->getScanCodeState(mId, scanCode) == AKEY_STATE_DOWN;
@@ -442,11 +438,6 @@
     inline bool isKeyCodePressed(int32_t keyCode) const {
         return mEventHub->getKeyCodeState(mId, keyCode) == AKEY_STATE_DOWN;
     }
-    inline int32_t getAbsoluteAxisValue(int32_t code) const {
-        int32_t value;
-        mEventHub->getAbsoluteAxisValue(mId, code, &value);
-        return value;
-    }
     inline bool isDeviceEnabled() { return mEventHub->isDeviceEnabled(mId); }
     inline status_t enableDevice() { return mEventHub->enableDevice(mId); }
     inline status_t disableDevice() { return mEventHub->disableDevice(mId); }
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
index 90685de..c8e7790 100644
--- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
@@ -16,6 +16,7 @@
 
 #include "CapturedTouchpadEventConverter.h"
 
+#include <optional>
 #include <sstream>
 
 #include <android-base/stringprintf.h>
@@ -53,32 +54,33 @@
         mMotionAccumulator(motionAccumulator),
         mHasTouchMinor(deviceContext.hasAbsoluteAxis(ABS_MT_TOUCH_MINOR)),
         mHasToolMinor(deviceContext.hasAbsoluteAxis(ABS_MT_WIDTH_MINOR)) {
-    RawAbsoluteAxisInfo orientationInfo;
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo);
-    if (orientationInfo.valid) {
-        if (orientationInfo.maxValue > 0) {
-            mOrientationScale = M_PI_2 / orientationInfo.maxValue;
-        } else if (orientationInfo.minValue < 0) {
-            mOrientationScale = -M_PI_2 / orientationInfo.minValue;
+    if (std::optional<RawAbsoluteAxisInfo> orientation =
+                deviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION);
+        orientation) {
+        if (orientation->maxValue > 0) {
+            mOrientationScale = M_PI_2 / orientation->maxValue;
+        } else if (orientation->minValue < 0) {
+            mOrientationScale = -M_PI_2 / orientation->minValue;
         }
     }
 
     // TODO(b/275369880): support touch.pressure.calibration and .scale properties when captured.
-    RawAbsoluteAxisInfo pressureInfo;
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo);
-    if (pressureInfo.valid && pressureInfo.maxValue > 0) {
-        mPressureScale = 1.0 / pressureInfo.maxValue;
+    if (std::optional<RawAbsoluteAxisInfo> pressure =
+                deviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE);
+        pressure && pressure->maxValue > 0) {
+        mPressureScale = 1.0 / pressure->maxValue;
     }
 
-    RawAbsoluteAxisInfo touchMajorInfo, toolMajorInfo;
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &touchMajorInfo);
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &toolMajorInfo);
-    mHasTouchMajor = touchMajorInfo.valid;
-    mHasToolMajor = toolMajorInfo.valid;
-    if (mHasTouchMajor && touchMajorInfo.maxValue != 0) {
-        mSizeScale = 1.0f / touchMajorInfo.maxValue;
-    } else if (mHasToolMajor && toolMajorInfo.maxValue != 0) {
-        mSizeScale = 1.0f / toolMajorInfo.maxValue;
+    std::optional<RawAbsoluteAxisInfo> touchMajor =
+            deviceContext.getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR);
+    std::optional<RawAbsoluteAxisInfo> toolMajor =
+            deviceContext.getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR);
+    mHasTouchMajor = touchMajor.has_value();
+    mHasToolMajor = toolMajor.has_value();
+    if (mHasTouchMajor && touchMajor->maxValue != 0) {
+        mSizeScale = 1.0f / touchMajor->maxValue;
+    } else if (mHasToolMajor && toolMajor->maxValue != 0) {
+        mSizeScale = 1.0f / toolMajor->maxValue;
     }
 }
 
@@ -113,15 +115,13 @@
     tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MAJOR, ABS_MT_WIDTH_MAJOR);
     tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MINOR, ABS_MT_WIDTH_MINOR);
 
-    RawAbsoluteAxisInfo pressureInfo;
-    mDeviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo);
-    if (pressureInfo.valid) {
+    if (mDeviceContext.hasAbsoluteAxis(ABS_MT_PRESSURE)) {
         info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0, 1, 0, 0, 0);
     }
 
-    RawAbsoluteAxisInfo orientationInfo;
-    mDeviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo);
-    if (orientationInfo.valid && (orientationInfo.maxValue > 0 || orientationInfo.minValue < 0)) {
+    if (std::optional<RawAbsoluteAxisInfo> orientation =
+                mDeviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION);
+        orientation && (orientation->maxValue > 0 || orientation->minValue < 0)) {
         info.addMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, SOURCE, -M_PI_2, M_PI_2, 0, 0, 0);
     }
 
@@ -133,11 +133,10 @@
 void CapturedTouchpadEventConverter::tryAddRawMotionRange(InputDeviceInfo& deviceInfo,
                                                           int32_t androidAxis,
                                                           int32_t evdevAxis) const {
-    RawAbsoluteAxisInfo info;
-    mDeviceContext.getAbsoluteAxisInfo(evdevAxis, &info);
-    if (info.valid) {
-        deviceInfo.addMotionRange(androidAxis, SOURCE, info.minValue, info.maxValue, info.flat,
-                                  info.fuzz, info.resolution);
+    std::optional<RawAbsoluteAxisInfo> info = mDeviceContext.getAbsoluteAxisInfo(evdevAxis);
+    if (info) {
+        deviceInfo.addMotionRange(androidAxis, SOURCE, info->minValue, info->maxValue, info->flat,
+                                  info->fuzz, info->resolution);
     }
 }
 
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
index 3af1d04..7cc8940 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
@@ -33,7 +33,7 @@
 
 void ExternalStylusInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     InputMapper::populateDeviceInfo(info);
-    if (mRawPressureAxis.valid) {
+    if (mRawPressureAxis) {
         info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_STYLUS, 0.0f, 1.0f, 0.0f,
                             0.0f, 0.0f);
     }
@@ -50,7 +50,7 @@
 std::list<NotifyArgs> ExternalStylusInputMapper::reconfigure(nsecs_t when,
                                                              const InputReaderConfiguration& config,
                                                              ConfigurationChanges changes) {
-    getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPressureAxis);
+    mRawPressureAxis = getAbsoluteAxisInfo(ABS_PRESSURE);
     mTouchButtonAccumulator.configure();
     return {};
 }
@@ -82,10 +82,10 @@
         mStylusState.toolType = ToolType::STYLUS;
     }
 
-    if (mRawPressureAxis.valid) {
+    if (mRawPressureAxis) {
         auto rawPressure = static_cast<float>(mSingleTouchMotionAccumulator.getAbsolutePressure());
-        mStylusState.pressure = (rawPressure - mRawPressureAxis.minValue) /
-                static_cast<float>(mRawPressureAxis.maxValue - mRawPressureAxis.minValue);
+        mStylusState.pressure = (rawPressure - mRawPressureAxis->minValue) /
+                static_cast<float>(mRawPressureAxis->maxValue - mRawPressureAxis->minValue);
     } else if (mTouchButtonAccumulator.hasButtonTouch()) {
         mStylusState.pressure = mTouchButtonAccumulator.isHovering() ? 0.0f : 1.0f;
     }
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
index c040a7b..d48fd9b 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <optional>
+
 #include "InputMapper.h"
 
 #include "SingleTouchMotionAccumulator.h"
@@ -43,7 +45,7 @@
 
 private:
     SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
-    RawAbsoluteAxisInfo mRawPressureAxis;
+    std::optional<RawAbsoluteAxisInfo> mRawPressureAxis;
     TouchButtonAccumulator mTouchButtonAccumulator;
 
     StylusState mStylusState;
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index b6c5c98..c44c48c 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -18,6 +18,7 @@
 
 #include "InputMapper.h"
 
+#include <optional>
 #include <sstream>
 
 #include <ftl/enum.h>
@@ -116,15 +117,16 @@
     return {};
 }
 
-status_t InputMapper::getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo) {
-    return getDeviceContext().getAbsoluteAxisInfo(axis, axisInfo);
+std::optional<RawAbsoluteAxisInfo> InputMapper::getAbsoluteAxisInfo(int32_t axis) {
+    return getDeviceContext().getAbsoluteAxisInfo(axis);
 }
 
 void InputMapper::bumpGeneration() {
     getDeviceContext().bumpGeneration();
 }
 
-void InputMapper::dumpRawAbsoluteAxisInfo(std::string& dump, const RawAbsoluteAxisInfo& axis,
+void InputMapper::dumpRawAbsoluteAxisInfo(std::string& dump,
+                                          const std::optional<RawAbsoluteAxisInfo>& axis,
                                           const char* name) {
     std::stringstream out;
     out << INDENT4 << name << ": " << axis << "\n";
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index 2c51448..e5afcc7 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <optional>
+
 #include "EventHub.h"
 #include "InputDevice.h"
 #include "InputListener.h"
@@ -126,10 +128,11 @@
     explicit InputMapper(InputDeviceContext& deviceContext,
                          const InputReaderConfiguration& readerConfig);
 
-    status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo);
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t axis);
     void bumpGeneration();
 
-    static void dumpRawAbsoluteAxisInfo(std::string& dump, const RawAbsoluteAxisInfo& axis,
+    static void dumpRawAbsoluteAxisInfo(std::string& dump,
+                                        const std::optional<RawAbsoluteAxisInfo>& axis,
                                         const char* name);
     static void dumpStylusState(std::string& dump, const StylusState& state);
 };
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index 41e018d..3091714 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -117,9 +117,8 @@
                 continue; // axis must be claimed by a different device
             }
 
-            RawAbsoluteAxisInfo rawAxisInfo;
-            getAbsoluteAxisInfo(abs, &rawAxisInfo);
-            if (rawAxisInfo.valid) {
+            if (std::optional<RawAbsoluteAxisInfo> rawAxisInfo = getAbsoluteAxisInfo(abs);
+                rawAxisInfo) {
                 // Map axis.
                 AxisInfo axisInfo;
                 const bool explicitlyMapped = !getDeviceContext().mapAxis(abs, &axisInfo);
@@ -129,7 +128,7 @@
                     axisInfo.mode = AxisInfo::MODE_NORMAL;
                     axisInfo.axis = -1;
                 }
-                mAxes.insert({abs, createAxis(axisInfo, rawAxisInfo, explicitlyMapped)});
+                mAxes.insert({abs, createAxis(axisInfo, rawAxisInfo.value(), explicitlyMapped)});
             }
         }
 
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 1986fe2..3ea3c20 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -133,7 +133,7 @@
 
         bool isHovering = mTouchButtonAccumulator.getToolType() != ToolType::MOUSE &&
                 (mTouchButtonAccumulator.isHovering() ||
-                 (mRawPointerAxes.pressure.valid && inSlot.getPressure() <= 0));
+                 (mRawPointerAxes.pressure && inSlot.getPressure() <= 0));
         outPointer.isHovering = isHovering;
 
         // Assign pointer id using tracking id if available.
@@ -189,21 +189,23 @@
 void MultiTouchInputMapper::configureRawPointerAxes() {
     TouchInputMapper::configureRawPointerAxes();
 
-    getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mRawPointerAxes.x);
-    getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mRawPointerAxes.y);
-    getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &mRawPointerAxes.touchMajor);
-    getAbsoluteAxisInfo(ABS_MT_TOUCH_MINOR, &mRawPointerAxes.touchMinor);
-    getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &mRawPointerAxes.toolMajor);
-    getAbsoluteAxisInfo(ABS_MT_WIDTH_MINOR, &mRawPointerAxes.toolMinor);
-    getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &mRawPointerAxes.orientation);
-    getAbsoluteAxisInfo(ABS_MT_PRESSURE, &mRawPointerAxes.pressure);
-    getAbsoluteAxisInfo(ABS_MT_DISTANCE, &mRawPointerAxes.distance);
-    getAbsoluteAxisInfo(ABS_MT_TRACKING_ID, &mRawPointerAxes.trackingId);
-    getAbsoluteAxisInfo(ABS_MT_SLOT, &mRawPointerAxes.slot);
+    // We can safely assume that ABS_MT_POSITION_X and _Y axes will be available, as EventHub won't
+    // classify a device as multitouch if they're not present.
+    mRawPointerAxes.x = getAbsoluteAxisInfo(ABS_MT_POSITION_X).value();
+    mRawPointerAxes.y = getAbsoluteAxisInfo(ABS_MT_POSITION_Y).value();
+    mRawPointerAxes.touchMajor = getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR);
+    mRawPointerAxes.touchMinor = getAbsoluteAxisInfo(ABS_MT_TOUCH_MINOR);
+    mRawPointerAxes.toolMajor = getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR);
+    mRawPointerAxes.toolMinor = getAbsoluteAxisInfo(ABS_MT_WIDTH_MINOR);
+    mRawPointerAxes.orientation = getAbsoluteAxisInfo(ABS_MT_ORIENTATION);
+    mRawPointerAxes.pressure = getAbsoluteAxisInfo(ABS_MT_PRESSURE);
+    mRawPointerAxes.distance = getAbsoluteAxisInfo(ABS_MT_DISTANCE);
+    mRawPointerAxes.trackingId = getAbsoluteAxisInfo(ABS_MT_TRACKING_ID);
+    mRawPointerAxes.slot = getAbsoluteAxisInfo(ABS_MT_SLOT);
 
-    if (mRawPointerAxes.trackingId.valid && mRawPointerAxes.slot.valid &&
-        mRawPointerAxes.slot.minValue == 0 && mRawPointerAxes.slot.maxValue > 0) {
-        size_t slotCount = mRawPointerAxes.slot.maxValue + 1;
+    if (mRawPointerAxes.trackingId && mRawPointerAxes.slot && mRawPointerAxes.slot->minValue == 0 &&
+        mRawPointerAxes.slot->maxValue > 0) {
+        size_t slotCount = mRawPointerAxes.slot->maxValue + 1;
         if (slotCount > MAX_SLOTS) {
             ALOGW("MultiTouch Device %s reported %zu slots but the framework "
                   "only supports a maximum of %zu slots at this time.",
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 27ff52f..20fd359 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -84,12 +84,18 @@
         }
     }
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
-        std::optional<DisplayViewport> internalViewport =
-                config.getDisplayViewportByType(ViewportType::INTERNAL);
-        if (internalViewport) {
-            mOrientation = internalViewport->orientation;
+        if (getDeviceContext().getAssociatedViewport()) {
+            mDisplayId = getDeviceContext().getAssociatedViewport()->displayId;
+            mOrientation = getDeviceContext().getAssociatedViewport()->orientation;
         } else {
-            mOrientation = ui::ROTATION_0;
+            mDisplayId = ui::LogicalDisplayId::INVALID;
+            std::optional<DisplayViewport> internalViewport =
+                    config.getDisplayViewportByType(ViewportType::INTERNAL);
+            if (internalViewport) {
+                mOrientation = internalViewport->orientation;
+            } else {
+                mOrientation = ui::ROTATION_0;
+            }
         }
     }
     return out;
@@ -124,8 +130,6 @@
     // Send motion event.
     if (scrolled) {
         int32_t metaState = getContext()->getGlobalMetaState();
-        // This is not a pointer, so it's not associated with a display.
-        ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
 
         if (mOrientation == ui::ROTATION_180) {
             scroll = -scroll;
@@ -147,7 +151,7 @@
 
         out.push_back(
                 NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
-                                 displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
+                                 mDisplayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
                                  metaState, /*buttonState=*/0, MotionClassification::NONE,
                                  AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                  &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index 14c540b..7e80415 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -47,6 +47,7 @@
     int32_t mSource;
     float mScalingFactor;
     ui::Rotation mOrientation;
+    ui::LogicalDisplayId mDisplayId = ui::LogicalDisplayId::INVALID;
     std::unique_ptr<SlopController> mSlopController;
 
     explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index d7f2993..4233f78 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -133,9 +133,8 @@
                           .test(InputDeviceClass::SENSOR))) {
                 continue;
             }
-            RawAbsoluteAxisInfo rawAxisInfo;
-            getAbsoluteAxisInfo(abs, &rawAxisInfo);
-            if (rawAxisInfo.valid) {
+            if (std::optional<RawAbsoluteAxisInfo> rawAxisInfo = getAbsoluteAxisInfo(abs);
+                rawAxisInfo) {
                 AxisInfo axisInfo;
                 // Axis doesn't need to be mapped, as sensor mapper doesn't generate any motion
                 // input events
@@ -146,7 +145,7 @@
                 if (ret.ok()) {
                     InputDeviceSensorType sensorType = (*ret).first;
                     int32_t sensorDataIndex = (*ret).second;
-                    const Axis& axis = createAxis(axisInfo, rawAxisInfo);
+                    const Axis& axis = createAxis(axisInfo, rawAxisInfo.value());
                     parseSensorConfiguration(sensorType, abs, sensorDataIndex, axis);
 
                     mAxes.insert({abs, axis});
diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
index 140bb0c..869feb4 100644
--- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
@@ -44,7 +44,7 @@
 
         bool isHovering = mTouchButtonAccumulator.getToolType() != ToolType::MOUSE &&
                 (mTouchButtonAccumulator.isHovering() ||
-                 (mRawPointerAxes.pressure.valid &&
+                 (mRawPointerAxes.pressure &&
                   mSingleTouchMotionAccumulator.getAbsolutePressure() <= 0));
         outState->rawPointerData.markIdBit(0, isHovering);
 
@@ -72,13 +72,15 @@
 void SingleTouchInputMapper::configureRawPointerAxes() {
     TouchInputMapper::configureRawPointerAxes();
 
-    getAbsoluteAxisInfo(ABS_X, &mRawPointerAxes.x);
-    getAbsoluteAxisInfo(ABS_Y, &mRawPointerAxes.y);
-    getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPointerAxes.pressure);
-    getAbsoluteAxisInfo(ABS_TOOL_WIDTH, &mRawPointerAxes.toolMajor);
-    getAbsoluteAxisInfo(ABS_DISTANCE, &mRawPointerAxes.distance);
-    getAbsoluteAxisInfo(ABS_TILT_X, &mRawPointerAxes.tiltX);
-    getAbsoluteAxisInfo(ABS_TILT_Y, &mRawPointerAxes.tiltY);
+    // We can safely assume that ABS_X and _Y axes will be available, as EventHub won't classify a
+    // device as a touch device if they're not present.
+    mRawPointerAxes.x = getAbsoluteAxisInfo(ABS_X).value();
+    mRawPointerAxes.y = getAbsoluteAxisInfo(ABS_Y).value();
+    mRawPointerAxes.pressure = getAbsoluteAxisInfo(ABS_PRESSURE);
+    mRawPointerAxes.toolMajor = getAbsoluteAxisInfo(ABS_TOOL_WIDTH);
+    mRawPointerAxes.distance = getAbsoluteAxisInfo(ABS_DISTANCE);
+    mRawPointerAxes.tiltX = getAbsoluteAxisInfo(ABS_TILT_X);
+    mRawPointerAxes.tiltY = getAbsoluteAxisInfo(ABS_TILT_Y);
 }
 
 bool SingleTouchInputMapper::hasStylus() const {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index a383490..beba3b8 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -600,10 +600,10 @@
     const float diagonalSize = hypotf(mDisplayBounds.width, mDisplayBounds.height);
 
     // Size factors.
-    if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.touchMajor.maxValue != 0) {
-        mSizeScale = 1.0f / mRawPointerAxes.touchMajor.maxValue;
-    } else if (mRawPointerAxes.toolMajor.valid && mRawPointerAxes.toolMajor.maxValue != 0) {
-        mSizeScale = 1.0f / mRawPointerAxes.toolMajor.maxValue;
+    if (mRawPointerAxes.touchMajor && mRawPointerAxes.touchMajor->maxValue != 0) {
+        mSizeScale = 1.0f / mRawPointerAxes.touchMajor->maxValue;
+    } else if (mRawPointerAxes.toolMajor && mRawPointerAxes.toolMajor->maxValue != 0) {
+        mSizeScale = 1.0f / mRawPointerAxes.toolMajor->maxValue;
     } else {
         mSizeScale = 0.0f;
     }
@@ -618,18 +618,18 @@
             .resolution = 0,
     };
 
-    if (mRawPointerAxes.touchMajor.valid) {
-        mRawPointerAxes.touchMajor.resolution =
-                clampResolution("touchMajor", mRawPointerAxes.touchMajor.resolution);
-        mOrientedRanges.touchMajor->resolution = mRawPointerAxes.touchMajor.resolution;
+    if (mRawPointerAxes.touchMajor) {
+        mRawPointerAxes.touchMajor->resolution =
+                clampResolution("touchMajor", mRawPointerAxes.touchMajor->resolution);
+        mOrientedRanges.touchMajor->resolution = mRawPointerAxes.touchMajor->resolution;
     }
 
     mOrientedRanges.touchMinor = mOrientedRanges.touchMajor;
     mOrientedRanges.touchMinor->axis = AMOTION_EVENT_AXIS_TOUCH_MINOR;
-    if (mRawPointerAxes.touchMinor.valid) {
-        mRawPointerAxes.touchMinor.resolution =
-                clampResolution("touchMinor", mRawPointerAxes.touchMinor.resolution);
-        mOrientedRanges.touchMinor->resolution = mRawPointerAxes.touchMinor.resolution;
+    if (mRawPointerAxes.touchMinor) {
+        mRawPointerAxes.touchMinor->resolution =
+                clampResolution("touchMinor", mRawPointerAxes.touchMinor->resolution);
+        mOrientedRanges.touchMinor->resolution = mRawPointerAxes.touchMinor->resolution;
     }
 
     mOrientedRanges.toolMajor = InputDeviceInfo::MotionRange{
@@ -641,18 +641,18 @@
             .fuzz = 0,
             .resolution = 0,
     };
-    if (mRawPointerAxes.toolMajor.valid) {
-        mRawPointerAxes.toolMajor.resolution =
-                clampResolution("toolMajor", mRawPointerAxes.toolMajor.resolution);
-        mOrientedRanges.toolMajor->resolution = mRawPointerAxes.toolMajor.resolution;
+    if (mRawPointerAxes.toolMajor) {
+        mRawPointerAxes.toolMajor->resolution =
+                clampResolution("toolMajor", mRawPointerAxes.toolMajor->resolution);
+        mOrientedRanges.toolMajor->resolution = mRawPointerAxes.toolMajor->resolution;
     }
 
     mOrientedRanges.toolMinor = mOrientedRanges.toolMajor;
     mOrientedRanges.toolMinor->axis = AMOTION_EVENT_AXIS_TOOL_MINOR;
-    if (mRawPointerAxes.toolMinor.valid) {
-        mRawPointerAxes.toolMinor.resolution =
-                clampResolution("toolMinor", mRawPointerAxes.toolMinor.resolution);
-        mOrientedRanges.toolMinor->resolution = mRawPointerAxes.toolMinor.resolution;
+    if (mRawPointerAxes.toolMinor) {
+        mRawPointerAxes.toolMinor->resolution =
+                clampResolution("toolMinor", mRawPointerAxes.toolMinor->resolution);
+        mOrientedRanges.toolMinor->resolution = mRawPointerAxes.toolMinor->resolution;
     }
 
     if (mCalibration.sizeCalibration == Calibration::SizeCalibration::GEOMETRIC) {
@@ -704,9 +704,10 @@
         mCalibration.pressureCalibration == Calibration::PressureCalibration::AMPLITUDE) {
         if (mCalibration.pressureScale) {
             mPressureScale = *mCalibration.pressureScale;
-            pressureMax = mPressureScale * mRawPointerAxes.pressure.maxValue;
-        } else if (mRawPointerAxes.pressure.valid && mRawPointerAxes.pressure.maxValue != 0) {
-            mPressureScale = 1.0f / mRawPointerAxes.pressure.maxValue;
+            pressureMax = mPressureScale *
+                    (mRawPointerAxes.pressure ? mRawPointerAxes.pressure->maxValue : 0);
+        } else if (mRawPointerAxes.pressure && mRawPointerAxes.pressure->maxValue != 0) {
+            mPressureScale = 1.0f / mRawPointerAxes.pressure->maxValue;
         }
     }
 
@@ -725,18 +726,18 @@
     mTiltXScale = 0;
     mTiltYCenter = 0;
     mTiltYScale = 0;
-    mHaveTilt = mRawPointerAxes.tiltX.valid && mRawPointerAxes.tiltY.valid;
+    mHaveTilt = mRawPointerAxes.tiltX && mRawPointerAxes.tiltY;
     if (mHaveTilt) {
-        mTiltXCenter = avg(mRawPointerAxes.tiltX.minValue, mRawPointerAxes.tiltX.maxValue);
-        mTiltYCenter = avg(mRawPointerAxes.tiltY.minValue, mRawPointerAxes.tiltY.maxValue);
+        mTiltXCenter = avg(mRawPointerAxes.tiltX->minValue, mRawPointerAxes.tiltX->maxValue);
+        mTiltYCenter = avg(mRawPointerAxes.tiltY->minValue, mRawPointerAxes.tiltY->maxValue);
         mTiltXScale = M_PI / 180;
         mTiltYScale = M_PI / 180;
 
-        if (mRawPointerAxes.tiltX.resolution) {
-            mTiltXScale = 1.0 / mRawPointerAxes.tiltX.resolution;
+        if (mRawPointerAxes.tiltX->resolution) {
+            mTiltXScale = 1.0 / mRawPointerAxes.tiltX->resolution;
         }
-        if (mRawPointerAxes.tiltY.resolution) {
-            mTiltYScale = 1.0 / mRawPointerAxes.tiltY.resolution;
+        if (mRawPointerAxes.tiltY->resolution) {
+            mTiltYScale = 1.0 / mRawPointerAxes.tiltY->resolution;
         }
 
         mOrientedRanges.tilt = InputDeviceInfo::MotionRange{
@@ -766,11 +767,11 @@
     } else if (mCalibration.orientationCalibration != Calibration::OrientationCalibration::NONE) {
         if (mCalibration.orientationCalibration ==
             Calibration::OrientationCalibration::INTERPOLATED) {
-            if (mRawPointerAxes.orientation.valid) {
-                if (mRawPointerAxes.orientation.maxValue > 0) {
-                    mOrientationScale = M_PI_2 / mRawPointerAxes.orientation.maxValue;
-                } else if (mRawPointerAxes.orientation.minValue < 0) {
-                    mOrientationScale = -M_PI_2 / mRawPointerAxes.orientation.minValue;
+            if (mRawPointerAxes.orientation) {
+                if (mRawPointerAxes.orientation->maxValue > 0) {
+                    mOrientationScale = M_PI_2 / mRawPointerAxes.orientation->maxValue;
+                } else if (mRawPointerAxes.orientation->minValue < 0) {
+                    mOrientationScale = -M_PI_2 / mRawPointerAxes.orientation->minValue;
                 } else {
                     mOrientationScale = 0;
                 }
@@ -795,14 +796,14 @@
             mDistanceScale = mCalibration.distanceScale.value_or(1.0f);
         }
 
+        const bool hasDistance = mRawPointerAxes.distance.has_value();
         mOrientedRanges.distance = InputDeviceInfo::MotionRange{
-
                 .axis = AMOTION_EVENT_AXIS_DISTANCE,
                 .source = mSource,
-                .min = mRawPointerAxes.distance.minValue * mDistanceScale,
-                .max = mRawPointerAxes.distance.maxValue * mDistanceScale,
+                .min = hasDistance ? mRawPointerAxes.distance->minValue * mDistanceScale : 0,
+                .max = hasDistance ? mRawPointerAxes.distance->maxValue * mDistanceScale : 0,
                 .flat = 0,
-                .fuzz = mRawPointerAxes.distance.fuzz * mDistanceScale,
+                .fuzz = hasDistance ? mRawPointerAxes.distance->fuzz * mDistanceScale : 0,
                 .resolution = 0,
         };
     }
@@ -931,7 +932,7 @@
             mSource |= AINPUT_SOURCE_STYLUS;
         }
     } else if (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION) {
-        mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
+        mSource = AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD;
         mDeviceMode = DeviceMode::NAVIGATION;
     } else {
         ALOGW("Touch device '%s' has invalid parameters or configuration.  The device will be "
@@ -943,12 +944,7 @@
     const std::optional<DisplayViewport> newViewportOpt = findViewport();
 
     // Ensure the device is valid and can be used.
-    if (!mRawPointerAxes.x.valid || !mRawPointerAxes.y.valid) {
-        ALOGW("Touch device '%s' did not report support for X or Y axis!  "
-              "The device will be inoperable.",
-              getDeviceName().c_str());
-        mDeviceMode = DeviceMode::DISABLED;
-    } else if (!newViewportOpt) {
+    if (!newViewportOpt) {
         ALOGI("Touch device '%s' could not query the properties of its associated "
               "display.  The device will be inoperable until the display size "
               "becomes available.",
@@ -1237,7 +1233,7 @@
 
 void TouchInputMapper::resolveCalibration() {
     // Size
-    if (mRawPointerAxes.touchMajor.valid || mRawPointerAxes.toolMajor.valid) {
+    if (mRawPointerAxes.touchMajor || mRawPointerAxes.toolMajor) {
         if (mCalibration.sizeCalibration == Calibration::SizeCalibration::DEFAULT) {
             mCalibration.sizeCalibration = Calibration::SizeCalibration::GEOMETRIC;
         }
@@ -1246,7 +1242,7 @@
     }
 
     // Pressure
-    if (mRawPointerAxes.pressure.valid) {
+    if (mRawPointerAxes.pressure) {
         if (mCalibration.pressureCalibration == Calibration::PressureCalibration::DEFAULT) {
             mCalibration.pressureCalibration = Calibration::PressureCalibration::PHYSICAL;
         }
@@ -1255,7 +1251,7 @@
     }
 
     // Orientation
-    if (mRawPointerAxes.orientation.valid) {
+    if (mRawPointerAxes.orientation) {
         if (mCalibration.orientationCalibration == Calibration::OrientationCalibration::DEFAULT) {
             mCalibration.orientationCalibration = Calibration::OrientationCalibration::INTERPOLATED;
         }
@@ -1264,7 +1260,7 @@
     }
 
     // Distance
-    if (mRawPointerAxes.distance.valid) {
+    if (mRawPointerAxes.distance) {
         if (mCalibration.distanceCalibration == Calibration::DistanceCalibration::DEFAULT) {
             mCalibration.distanceCalibration = Calibration::DistanceCalibration::SCALED;
         }
@@ -2251,25 +2247,25 @@
             case Calibration::SizeCalibration::DIAMETER:
             case Calibration::SizeCalibration::BOX:
             case Calibration::SizeCalibration::AREA:
-                if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.toolMajor.valid) {
+                if (mRawPointerAxes.touchMajor && mRawPointerAxes.toolMajor) {
                     touchMajor = in.touchMajor;
-                    touchMinor = mRawPointerAxes.touchMinor.valid ? in.touchMinor : in.touchMajor;
+                    touchMinor = mRawPointerAxes.touchMinor ? in.touchMinor : in.touchMajor;
                     toolMajor = in.toolMajor;
-                    toolMinor = mRawPointerAxes.toolMinor.valid ? in.toolMinor : in.toolMajor;
-                    size = mRawPointerAxes.touchMinor.valid ? avg(in.touchMajor, in.touchMinor)
-                                                            : in.touchMajor;
-                } else if (mRawPointerAxes.touchMajor.valid) {
+                    toolMinor = mRawPointerAxes.toolMinor ? in.toolMinor : in.toolMajor;
+                    size = mRawPointerAxes.touchMinor ? avg(in.touchMajor, in.touchMinor)
+                                                      : in.touchMajor;
+                } else if (mRawPointerAxes.touchMajor) {
                     toolMajor = touchMajor = in.touchMajor;
                     toolMinor = touchMinor =
-                            mRawPointerAxes.touchMinor.valid ? in.touchMinor : in.touchMajor;
-                    size = mRawPointerAxes.touchMinor.valid ? avg(in.touchMajor, in.touchMinor)
-                                                            : in.touchMajor;
-                } else if (mRawPointerAxes.toolMajor.valid) {
+                            mRawPointerAxes.touchMinor ? in.touchMinor : in.touchMajor;
+                    size = mRawPointerAxes.touchMinor ? avg(in.touchMajor, in.touchMinor)
+                                                      : in.touchMajor;
+                } else if (mRawPointerAxes.toolMajor) {
                     touchMajor = toolMajor = in.toolMajor;
                     touchMinor = toolMinor =
-                            mRawPointerAxes.toolMinor.valid ? in.toolMinor : in.toolMajor;
-                    size = mRawPointerAxes.toolMinor.valid ? avg(in.toolMajor, in.toolMinor)
-                                                           : in.toolMajor;
+                            mRawPointerAxes.toolMinor ? in.toolMinor : in.toolMajor;
+                    size = mRawPointerAxes.toolMinor ? avg(in.toolMajor, in.toolMinor)
+                                                     : in.toolMajor;
                 } else {
                     ALOG_ASSERT(false,
                                 "No touch or tool axes.  "
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 30c58a5..87b72af 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -61,17 +61,17 @@
 struct RawPointerAxes {
     RawAbsoluteAxisInfo x{};
     RawAbsoluteAxisInfo y{};
-    RawAbsoluteAxisInfo pressure{};
-    RawAbsoluteAxisInfo touchMajor{};
-    RawAbsoluteAxisInfo touchMinor{};
-    RawAbsoluteAxisInfo toolMajor{};
-    RawAbsoluteAxisInfo toolMinor{};
-    RawAbsoluteAxisInfo orientation{};
-    RawAbsoluteAxisInfo distance{};
-    RawAbsoluteAxisInfo tiltX{};
-    RawAbsoluteAxisInfo tiltY{};
-    RawAbsoluteAxisInfo trackingId{};
-    RawAbsoluteAxisInfo slot{};
+    std::optional<RawAbsoluteAxisInfo> pressure{};
+    std::optional<RawAbsoluteAxisInfo> touchMajor{};
+    std::optional<RawAbsoluteAxisInfo> touchMinor{};
+    std::optional<RawAbsoluteAxisInfo> toolMajor{};
+    std::optional<RawAbsoluteAxisInfo> toolMinor{};
+    std::optional<RawAbsoluteAxisInfo> orientation{};
+    std::optional<RawAbsoluteAxisInfo> distance{};
+    std::optional<RawAbsoluteAxisInfo> tiltX{};
+    std::optional<RawAbsoluteAxisInfo> tiltY{};
+    std::optional<RawAbsoluteAxisInfo> trackingId{};
+    std::optional<RawAbsoluteAxisInfo> slot{};
 
     inline int32_t getRawWidth() const { return x.maxValue - x.minValue + 1; }
     inline int32_t getRawHeight() const { return y.maxValue - y.minValue + 1; }
@@ -339,8 +339,8 @@
         int32_t buttonState{};
 
         // Scroll state.
-        int32_t rawVScroll{};
-        int32_t rawHScroll{};
+        float rawVScroll{};
+        float rawHScroll{};
 
         inline void clear() { *this = RawState(); }
     };
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 24efae8..daab636 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -240,14 +240,15 @@
         mGestureConverter(*getContext(), deviceContext, getDeviceId()),
         mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()),
         mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())) {
-    RawAbsoluteAxisInfo slotAxisInfo;
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
-    if (!slotAxisInfo.valid || slotAxisInfo.maxValue < 0) {
+    if (std::optional<RawAbsoluteAxisInfo> slotAxis =
+                deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT);
+        slotAxis && slotAxis->maxValue >= 0) {
+        mMotionAccumulator.configure(deviceContext, slotAxis->maxValue + 1, true);
+    } else {
         LOG(WARNING) << "Touchpad " << deviceContext.getName()
                      << " doesn't have a valid ABS_MT_SLOT axis, and probably won't work properly.";
-        slotAxisInfo.maxValue = 0;
+        mMotionAccumulator.configure(deviceContext, 1, true);
     }
-    mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true);
 
     mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD);
     mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext));
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
index f85cab2..06315e2 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
@@ -16,18 +16,29 @@
 
 #include "CursorScrollAccumulator.h"
 
+#include <android_companion_virtualdevice_flags.h>
 #include "EventHub.h"
 #include "InputDevice.h"
 
 namespace android {
 
-CursorScrollAccumulator::CursorScrollAccumulator() : mHaveRelWheel(false), mHaveRelHWheel(false) {
+namespace vd_flags = android::companion::virtualdevice::flags;
+
+CursorScrollAccumulator::CursorScrollAccumulator()
+      : mHaveRelWheel(false),
+        mHaveRelHWheel(false),
+        mHaveRelWheelHighRes(false),
+        mHaveRelHWheelHighRes(false) {
     clearRelativeAxes();
 }
 
 void CursorScrollAccumulator::configure(InputDeviceContext& deviceContext) {
     mHaveRelWheel = deviceContext.hasRelativeAxis(REL_WHEEL);
     mHaveRelHWheel = deviceContext.hasRelativeAxis(REL_HWHEEL);
+    if (vd_flags::high_resolution_scroll()) {
+        mHaveRelWheelHighRes = deviceContext.hasRelativeAxis(REL_WHEEL_HI_RES);
+        mHaveRelHWheelHighRes = deviceContext.hasRelativeAxis(REL_HWHEEL_HI_RES);
+    }
 }
 
 void CursorScrollAccumulator::reset(InputDeviceContext& deviceContext) {
@@ -42,11 +53,31 @@
 void CursorScrollAccumulator::process(const RawEvent& rawEvent) {
     if (rawEvent.type == EV_REL) {
         switch (rawEvent.code) {
+            case REL_WHEEL_HI_RES:
+                if (mHaveRelWheelHighRes) {
+                    mRelWheel = rawEvent.value /
+                            static_cast<float>(kEvdevMouseHighResScrollUnitsPerDetent);
+                }
+                break;
+            case REL_HWHEEL_HI_RES:
+                if (mHaveRelHWheelHighRes) {
+                    mRelHWheel = rawEvent.value /
+                            static_cast<float>(kEvdevMouseHighResScrollUnitsPerDetent);
+                }
+                break;
             case REL_WHEEL:
-                mRelWheel = rawEvent.value;
+                // We should ignore regular scroll events, if we have already have high-res scroll
+                // enabled.
+                if (!mHaveRelWheelHighRes) {
+                    mRelWheel = rawEvent.value;
+                }
                 break;
             case REL_HWHEEL:
-                mRelHWheel = rawEvent.value;
+                // We should ignore regular scroll events, if we have already have high-res scroll
+                // enabled.
+                if (!mHaveRelHWheelHighRes) {
+                    mRelHWheel = rawEvent.value;
+                }
                 break;
         }
     }
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
index e563620..6990d20 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
@@ -24,7 +24,6 @@
 struct RawEvent;
 
 /* Keeps track of cursor scrolling motions. */
-
 class CursorScrollAccumulator {
 public:
     CursorScrollAccumulator();
@@ -39,17 +38,19 @@
 
     inline int32_t getRelativeX() const { return mRelX; }
     inline int32_t getRelativeY() const { return mRelY; }
-    inline int32_t getRelativeVWheel() const { return mRelWheel; }
-    inline int32_t getRelativeHWheel() const { return mRelHWheel; }
+    inline float getRelativeVWheel() const { return mRelWheel; }
+    inline float getRelativeHWheel() const { return mRelHWheel; }
 
 private:
     bool mHaveRelWheel;
     bool mHaveRelHWheel;
+    bool mHaveRelWheelHighRes;
+    bool mHaveRelHWheelHighRes;
 
     int32_t mRelX;
     int32_t mRelY;
-    int32_t mRelWheel;
-    int32_t mRelHWheel;
+    float mRelWheel;
+    float mRelHWheel;
 
     void clearRelativeAxes();
 };
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
index 4919068..8dc6e4d 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -139,13 +139,11 @@
     if (!mUsingSlotsProtocol) {
         return;
     }
-    int32_t initialSlot;
-    if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
-        status == OK) {
-        mCurrentSlot = initialSlot;
+    if (const std::optional<int32_t> initialSlot = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT);
+        initialSlot.has_value()) {
+        mCurrentSlot = initialSlot.value();
     } else {
-        ALOGE("Could not retrieve current multi-touch slot index. status=%s",
-              statusToString(status).c_str());
+        ALOGE("Could not retrieve current multi-touch slot index");
     }
 }
 
diff --git a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
index 2b82ddf..4cf9243 100644
--- a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
@@ -26,13 +26,13 @@
 }
 
 void SingleTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) {
-    mAbsX = deviceContext.getAbsoluteAxisValue(ABS_X);
-    mAbsY = deviceContext.getAbsoluteAxisValue(ABS_Y);
-    mAbsPressure = deviceContext.getAbsoluteAxisValue(ABS_PRESSURE);
-    mAbsToolWidth = deviceContext.getAbsoluteAxisValue(ABS_TOOL_WIDTH);
-    mAbsDistance = deviceContext.getAbsoluteAxisValue(ABS_DISTANCE);
-    mAbsTiltX = deviceContext.getAbsoluteAxisValue(ABS_TILT_X);
-    mAbsTiltY = deviceContext.getAbsoluteAxisValue(ABS_TILT_Y);
+    mAbsX = deviceContext.getAbsoluteAxisValue(ABS_X).value_or(0);
+    mAbsY = deviceContext.getAbsoluteAxisValue(ABS_Y).value_or(0);
+    mAbsPressure = deviceContext.getAbsoluteAxisValue(ABS_PRESSURE).value_or(0);
+    mAbsToolWidth = deviceContext.getAbsoluteAxisValue(ABS_TOOL_WIDTH).value_or(0);
+    mAbsDistance = deviceContext.getAbsoluteAxisValue(ABS_DISTANCE).value_or(0);
+    mAbsTiltX = deviceContext.getAbsoluteAxisValue(ABS_TILT_X).value_or(0);
+    mAbsTiltY = deviceContext.getAbsoluteAxisValue(ABS_TILT_Y).value_or(0);
 }
 
 void SingleTouchMotionAccumulator::clearAbsoluteAxes() {
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index e8e7376..9924d0d 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -66,10 +66,11 @@
                                    const InputDeviceContext& deviceContext, int32_t deviceId)
       : mDeviceId(deviceId),
         mReaderContext(readerContext),
-        mEnableFlingStop(input_flags::enable_touchpad_fling_stop()) {
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo);
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo);
-}
+        mEnableFlingStop(input_flags::enable_touchpad_fling_stop()),
+        // We can safely assume that ABS_MT_POSITION_X and _Y axes will be available, as EventHub
+        // won't classify a device as a touchpad if they're not present.
+        mXAxisInfo(deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X).value()),
+        mYAxisInfo(deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y).value()) {}
 
 std::string GestureConverter::dump() const {
     std::stringstream out;
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp b/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp
index 04655dc..d8a1f50 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp
+++ b/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp
@@ -16,6 +16,8 @@
 
 #include "HardwareProperties.h"
 
+#include <optional>
+
 namespace android {
 
 namespace {
@@ -33,26 +35,34 @@
 
 HardwareProperties createHardwareProperties(const InputDeviceContext& context) {
     HardwareProperties props;
-    RawAbsoluteAxisInfo absMtPositionX;
-    context.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &absMtPositionX);
+    // We can safely assume that ABS_MT_POSITION_X and _Y axes will be available, as EventHub won't
+    // classify a device as a touchpad if they're not present.
+    RawAbsoluteAxisInfo absMtPositionX = context.getAbsoluteAxisInfo(ABS_MT_POSITION_X).value();
     props.left = absMtPositionX.minValue;
     props.right = absMtPositionX.maxValue;
     props.res_x = absMtPositionX.resolution;
 
-    RawAbsoluteAxisInfo absMtPositionY;
-    context.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &absMtPositionY);
+    RawAbsoluteAxisInfo absMtPositionY = context.getAbsoluteAxisInfo(ABS_MT_POSITION_Y).value();
     props.top = absMtPositionY.minValue;
     props.bottom = absMtPositionY.maxValue;
     props.res_y = absMtPositionY.resolution;
 
-    RawAbsoluteAxisInfo absMtOrientation;
-    context.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &absMtOrientation);
-    props.orientation_minimum = absMtOrientation.minValue;
-    props.orientation_maximum = absMtOrientation.maxValue;
+    if (std::optional<RawAbsoluteAxisInfo> absMtOrientation =
+                context.getAbsoluteAxisInfo(ABS_MT_ORIENTATION);
+        absMtOrientation) {
+        props.orientation_minimum = absMtOrientation->minValue;
+        props.orientation_maximum = absMtOrientation->maxValue;
+    } else {
+        props.orientation_minimum = 0;
+        props.orientation_maximum = 0;
+    }
 
-    RawAbsoluteAxisInfo absMtSlot;
-    context.getAbsoluteAxisInfo(ABS_MT_SLOT, &absMtSlot);
-    props.max_finger_cnt = absMtSlot.maxValue - absMtSlot.minValue + 1;
+    if (std::optional<RawAbsoluteAxisInfo> absMtSlot = context.getAbsoluteAxisInfo(ABS_MT_SLOT);
+        absMtSlot) {
+        props.max_finger_cnt = absMtSlot->maxValue - absMtSlot->minValue + 1;
+    } else {
+        props.max_finger_cnt = 1;
+    }
     props.max_touch_cnt = getMaxTouchCount(context);
 
     // T5R2 ("Track 5, Report 2") is a feature of some old Synaptics touchpads that could track 5
@@ -71,9 +81,7 @@
     // are haptic.
     props.is_haptic_pad = false;
 
-    RawAbsoluteAxisInfo absMtPressure;
-    context.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &absMtPressure);
-    props.reports_pressure = absMtPressure.valid;
+    props.reports_pressure = context.hasAbsoluteAxis(ABS_MT_PRESSURE);
     return props;
 }
 
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index cf0d46a..65e0429 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -76,6 +76,7 @@
         "PointerChoreographer_test.cpp",
         "PreferStylusOverTouch_test.cpp",
         "PropertyProvider_test.cpp",
+        "RotaryEncoderInputMapper_test.cpp",
         "SlopController_test.cpp",
         "SyncQueue_test.cpp",
         "TimerProvider_test.cpp",
@@ -108,6 +109,7 @@
         },
     },
     static_libs: [
+        "android.companion.virtualdevice.flags-aconfig-cc-test",
         "libflagtest",
         "libgmock",
     ],
@@ -115,5 +117,8 @@
     test_options: {
         unit_test: true,
     },
-    test_suites: ["device-tests"],
+    test_suites: [
+        "device-tests",
+        "device-platinum-tests",
+    ],
 }
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index 83074ff..727237f 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -22,6 +22,7 @@
 #include <variant>
 
 #include <android-base/logging.h>
+#include <android_companion_virtualdevice_flags.h>
 #include <com_android_input_flags.h>
 #include <gtest/gtest.h>
 #include <input/DisplayViewport.h>
@@ -127,6 +128,7 @@
 } // namespace
 
 namespace input_flags = com::android::input::flags;
+namespace vd_flags = android::companion::virtualdevice::flags;
 
 /**
  * Unit tests for CursorInputMapper.
@@ -151,6 +153,10 @@
                 .WillRepeatedly(Return(false));
         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
                 .WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+                .WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+                .WillRepeatedly(Return(false));
 
         mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
         mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0));
@@ -194,6 +200,7 @@
 protected:
     void SetUp() override {
         input_flags::enable_new_mouse_pointer_ballistics(false);
+        vd_flags::high_resolution_scroll(false);
         CursorInputMapperUnitTestBase::SetUp();
     }
 };
@@ -840,6 +847,72 @@
                               WithOrientation(0.0f), WithDistance(0.0f)))));
 }
 
+TEST_F(CursorInputMapperUnitTest, ProcessRegularScroll) {
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(1.0f, 1.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessHighResScroll) {
+    vd_flags::high_resolution_scroll(true);
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(0.5f, 0.5f)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, HighResScrollIgnoresRegularScroll) {
+    vd_flags::high_resolution_scroll(true);
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(0.5f, 0.5f)))));
+}
+
 /**
  * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
  * pointer acceleration or speed processing should not be applied.
@@ -1030,6 +1103,72 @@
                               WithRelativeMotion(10, 20)))));
 }
 
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, ProcessRegularScroll) {
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(1.0f, 1.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, ProcessHighResScroll) {
+    vd_flags::high_resolution_scroll(true);
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(0.5f, 0.5f)))));
+}
+
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, HighResScrollIgnoresRegularScroll) {
+    vd_flags::high_resolution_scroll(true);
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(0.5f, 0.5f)))));
+}
+
 namespace {
 
 // Minimum timestamp separation between subsequent input events from a Bluetooth device.
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index daa000f..7079278 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -16,6 +16,8 @@
 
 #include "FakeEventHub.h"
 
+#include <optional>
+
 #include <android-base/thread_annotations.h>
 #include <gtest/gtest.h>
 #include <linux/input-event-codes.h>
@@ -103,7 +105,6 @@
     Device* device = getDevice(deviceId);
 
     RawAbsoluteAxisInfo info;
-    info.valid = true;
     info.minValue = minValue;
     info.maxValue = maxValue;
     info.flat = flat;
@@ -263,18 +264,16 @@
     return device->configuration;
 }
 
-status_t FakeEventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                           RawAbsoluteAxisInfo* outAxisInfo) const {
+std::optional<RawAbsoluteAxisInfo> FakeEventHub::getAbsoluteAxisInfo(int32_t deviceId,
+                                                                     int axis) const {
     Device* device = getDevice(deviceId);
     if (device) {
         ssize_t index = device->absoluteAxes.indexOfKey(axis);
         if (index >= 0) {
-            *outAxisInfo = device->absoluteAxes.valueAt(index);
-            return OK;
+            return device->absoluteAxes.valueAt(index);
         }
     }
-    outAxisInfo->clear();
-    return -1;
+    return std::nullopt;
 }
 
 bool FakeEventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
@@ -417,18 +416,15 @@
     return AKEY_STATE_UNKNOWN;
 }
 
-status_t FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                            int32_t* outValue) const {
+std::optional<int32_t> FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const {
     Device* device = getDevice(deviceId);
     if (device) {
         ssize_t index = device->absoluteAxisValue.indexOfKey(axis);
         if (index >= 0) {
-            *outValue = device->absoluteAxisValue.valueAt(index);
-            return OK;
+            return device->absoluteAxisValue.valueAt(index);
         }
     }
-    *outValue = 0;
-    return -1;
+    return std::nullopt;
 }
 
 void FakeEventHub::setMtSlotValues(int32_t deviceId, int32_t axis,
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index f07b344..c2c875f 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -168,8 +168,8 @@
     InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override;
     int32_t getDeviceControllerNumber(int32_t) const override;
     std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override;
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override;
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                           int axis) const override;
     bool hasRelativeAxis(int32_t deviceId, int axis) const override;
     bool hasInputProperty(int32_t, int) const override;
     bool hasMscEvent(int32_t deviceId, int mscEvent) const override final;
@@ -187,7 +187,7 @@
     std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const override;
     int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override;
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override;
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override;
+    std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const override;
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override;
 
     // Return true if the device has non-empty key layout.
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index d2cb0ac..6099c91 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -16,6 +16,7 @@
 
 #include "FakeInputReaderPolicy.h"
 
+#include <android-base/properties.h>
 #include <android-base/thread_annotations.h>
 #include <gtest/gtest.h>
 
@@ -24,6 +25,12 @@
 
 namespace android {
 
+namespace {
+
+static const int HW_TIMEOUT_MULTIPLIER = base::GetIntProperty("ro.hw_timeout_multiplier", 1);
+
+} // namespace
+
 void FakeInputReaderPolicy::assertInputDevicesChanged() {
     waitForInputDevices([](bool devicesChanged) {
         if (!devicesChanged) {
@@ -241,9 +248,11 @@
     base::ScopedLockAssertion assumeLocked(mLock);
 
     const bool devicesChanged =
-            mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
-                return mInputDevicesChanged;
-            });
+            mDevicesChangedCondition.wait_for(lock,
+                                              ADD_INPUT_DEVICE_TIMEOUT * HW_TIMEOUT_MULTIPLIER,
+                                              [this]() REQUIRES(mLock) {
+                                                  return mInputDevicesChanged;
+                                              });
     ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged));
     mInputDevicesChanged = false;
 }
diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h
index ee65d3a..3a3238a 100644
--- a/services/inputflinger/tests/FakeWindows.h
+++ b/services/inputflinger/tests/FakeWindows.h
@@ -304,6 +304,13 @@
                                           WithFlags(expectedFlags)));
     }
 
+    inline void consumeMotionPointerDown(int32_t pointerIdx,
+                                         const ::testing::Matcher<MotionEvent>& matcher) {
+        const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
+                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+        consumeMotionEvent(testing::AllOf(WithMotionAction(action), matcher));
+    }
+
     inline void consumeMotionPointerUp(int32_t pointerIdx,
                                        const ::testing::Matcher<MotionEvent>& matcher) {
         const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
diff --git a/services/inputflinger/tests/HardwareProperties_test.cpp b/services/inputflinger/tests/HardwareProperties_test.cpp
index 8dfa8c8..e87f822 100644
--- a/services/inputflinger/tests/HardwareProperties_test.cpp
+++ b/services/inputflinger/tests/HardwareProperties_test.cpp
@@ -48,24 +48,19 @@
     static constexpr int32_t EVENTHUB_ID = 1;
 
     void setupValidAxis(int axis, int32_t min, int32_t max, int32_t resolution) {
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
-                .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
-                    outAxisInfo->valid = true;
-                    outAxisInfo->minValue = min;
-                    outAxisInfo->maxValue = max;
-                    outAxisInfo->flat = 0;
-                    outAxisInfo->fuzz = 0;
-                    outAxisInfo->resolution = resolution;
-                    return OK;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis))
+                .WillRepeatedly(Return(std::optional<RawAbsoluteAxisInfo>{{
+                        .minValue = min,
+                        .maxValue = max,
+                        .flat = 0,
+                        .fuzz = 0,
+                        .resolution = resolution,
+                }}));
     }
 
     void setupInvalidAxis(int axis) {
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
-                .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
-                    outAxisInfo->valid = false;
-                    return -1;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis))
+                .WillRepeatedly(Return(std::nullopt));
     }
 
     void setProperty(int property, bool value) {
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 5eab6be..04ed670 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -4172,6 +4172,121 @@
     window2->consumeMotionEvent(WithDownTime(secondDown->getDownTime()));
 }
 
+/**
+ * When events are not split, the downTime should be adjusted such that the downTime corresponds
+ * to the event time of the first ACTION_DOWN. If a new window appears, it should not affect
+ * the event routing because the first window prevents splitting.
+ */
+TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTimeForNewWindow) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window1 =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window1", DISPLAY_ID);
+    window1->setTouchableRegion(Region{{0, 0, 100, 100}});
+    window1->setPreventSplitting(true);
+
+    sp<FakeWindowHandle> window2 =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window2", DISPLAY_ID);
+    window2->setTouchableRegion(Region{{100, 0, 200, 100}});
+
+    mDispatcher->onWindowInfosChanged({{*window1->getInfo()}, {}, 0, 0});
+
+    // Touch down on the first window
+    NotifyMotionArgs downArgs = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                        .build();
+    mDispatcher->notifyMotion(downArgs);
+
+    window1->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDownTime(downArgs.downTime)));
+
+    // Second window is added
+    mDispatcher->onWindowInfosChanged({{*window1->getInfo(), *window2->getInfo()}, {}, 0, 0});
+
+    // Now touch down on the window with another pointer
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+                                      .downTime(downArgs.downTime)
+                                      .build());
+    window1->consumeMotionPointerDown(1, AllOf(WithDownTime(downArgs.downTime)));
+
+    // Finish the gesture
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+                                      .downTime(downArgs.downTime)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .downTime(downArgs.downTime)
+                                      .build());
+    window1->consumeMotionPointerUp(1, AllOf(WithDownTime(downArgs.downTime)));
+    window1->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithDownTime(downArgs.downTime)));
+    window2->assertNoEvents();
+}
+
+/**
+ * When splitting touch events, the downTime should be adjusted such that the downTime corresponds
+ * to the event time of the first ACTION_DOWN sent to the new window.
+ * If a new window that does not support split appears on the screen and gets touched with the
+ * second finger, it should not get any events because it doesn't want split touches. At the same
+ * time, the first window should not get the pointer_down event because it supports split touches
+ * (and the touch occurred outside of the bounds of window1).
+ */
+TEST_F(InputDispatcherTest, SplitTouchesDropsEventForNonSplittableSecondWindow) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window1 =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window1", DISPLAY_ID);
+    window1->setTouchableRegion(Region{{0, 0, 100, 100}});
+
+    sp<FakeWindowHandle> window2 =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window2", DISPLAY_ID);
+    window2->setTouchableRegion(Region{{100, 0, 200, 100}});
+
+    mDispatcher->onWindowInfosChanged({{*window1->getInfo()}, {}, 0, 0});
+
+    // Touch down on the first window
+    NotifyMotionArgs downArgs = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                        .build();
+    mDispatcher->notifyMotion(downArgs);
+
+    window1->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDownTime(downArgs.downTime)));
+
+    // Second window is added
+    window2->setPreventSplitting(true);
+    mDispatcher->onWindowInfosChanged({{*window1->getInfo(), *window2->getInfo()}, {}, 0, 0});
+
+    // Now touch down on the window with another pointer
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+                                      .downTime(downArgs.downTime)
+                                      .build());
+    // Event is dropped because window2 doesn't support split touch, and window1 does.
+
+    // Complete the gesture
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+                                      .downTime(downArgs.downTime)
+                                      .build());
+    // A redundant MOVE event is generated that doesn't carry any new information
+    window1->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDownTime(downArgs.downTime)));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .downTime(downArgs.downTime)
+                                      .build());
+
+    window1->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithDownTime(downArgs.downTime)));
+    window1->assertNoEvents();
+    window2->assertNoEvents();
+}
+
 TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> windowLeft = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index b5c9232..5722444 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -57,16 +57,15 @@
 
 void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max,
                                     int32_t resolution) {
-    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, _))
-            .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
-                outAxisInfo->valid = valid;
-                outAxisInfo->minValue = min;
-                outAxisInfo->maxValue = max;
-                outAxisInfo->flat = 0;
-                outAxisInfo->fuzz = 0;
-                outAxisInfo->resolution = resolution;
-                return valid ? OK : -1;
-            });
+    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis))
+            .WillRepeatedly(Return(valid ? std::optional<RawAbsoluteAxisInfo>{{
+                                                   .minValue = min,
+                                                   .maxValue = max,
+                                                   .flat = 0,
+                                                   .fuzz = 0,
+                                                   .resolution = resolution,
+                                           }}
+                                         : std::nullopt));
 }
 
 void InputMapperUnitTest::expectScanCodes(bool present, std::set<int> scanCodes) {
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index fe238f3..fa9d263 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -6245,7 +6245,7 @@
     SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
 
-    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources());
+    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
 TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_updatesDeviceType) {
@@ -6268,7 +6268,7 @@
                                InputReaderConfiguration::Change::DEVICE_TYPE /*changes*/);
 
     // Check whether device type update was successful.
-    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mDevice->getSources());
+    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD, mDevice->getSources());
 }
 
 TEST_F(SingleTouchInputMapperTest, HoverEventsOutsidePhysicalFrameAreIgnored) {
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 16d3193..bacc6d4 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -91,8 +91,8 @@
     MOCK_METHOD(InputDeviceIdentifier, getDeviceIdentifier, (int32_t deviceId), (const));
     MOCK_METHOD(int32_t, getDeviceControllerNumber, (int32_t deviceId), (const));
     MOCK_METHOD(std::optional<PropertyMap>, getConfiguration, (int32_t deviceId), (const));
-    MOCK_METHOD(status_t, getAbsoluteAxisInfo,
-                (int32_t deviceId, int axis, RawAbsoluteAxisInfo* outAxisInfo), (const));
+    MOCK_METHOD(std::optional<RawAbsoluteAxisInfo>, getAbsoluteAxisInfo,
+                (int32_t deviceId, int axis), (const));
     MOCK_METHOD(bool, hasRelativeAxis, (int32_t deviceId, int axis), (const));
     MOCK_METHOD(bool, hasInputProperty, (int32_t deviceId, int property), (const));
     MOCK_METHOD(bool, hasMscEvent, (int32_t deviceId, int mscEvent), (const));
@@ -131,7 +131,7 @@
     MOCK_METHOD(int32_t, getKeyCodeState, (int32_t deviceId, int32_t keyCode), (const, override));
     MOCK_METHOD(int32_t, getSwitchState, (int32_t deviceId, int32_t sw), (const, override));
 
-    MOCK_METHOD(status_t, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis, int32_t* outValue),
+    MOCK_METHOD(std::optional<int32_t>, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis),
                 (const, override));
     MOCK_METHOD(base::Result<std::vector<int32_t>>, getMtSlotValues,
                 (int32_t deviceId, int32_t axis, size_t slotCount), (const, override));
diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
index b5f8971..d4d3c38 100644
--- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
+++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
@@ -99,11 +99,8 @@
         setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
 
         // reset current slot at the beginning
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
-                .WillRepeatedly([](int32_t, int32_t, int32_t* outValue) {
-                    *outValue = 0;
-                    return OK;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
+                .WillRepeatedly(Return(0));
 
         // mark all slots not in use
         mockSlotValues({});
@@ -211,11 +208,8 @@
     const auto pointerCoordsBeforeReset = std::get<NotifyMotionArgs>(args.back()).pointerCoords;
 
     // On buffer overflow mapper will be reset and MT slots data will be repopulated
-    EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
-            .WillRepeatedly([=](int32_t, int32_t, int32_t* outValue) {
-                *outValue = 1;
-                return OK;
-            });
+    EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
+            .WillRepeatedly(Return(1));
 
     mockSlotValues(
             {{1, {Point{x1, y1}, FIRST_TRACKING_ID}}, {2, {Point{x2, y2}, SECOND_TRACKING_ID}}});
diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
new file mode 100644
index 0000000..2b8071b
--- /dev/null
+++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RotaryEncoderInputMapper.h"
+
+#include <list>
+#include <string>
+#include <tuple>
+#include <variant>
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/DisplayViewport.h>
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+#include <utils/Timers.h>
+
+#include "InputMapperTest.h"
+#include "InputReaderBase.h"
+#include "InterfaceMocks.h"
+#include "NotifyArgs.h"
+#include "TestEventMatchers.h"
+#include "ui/Rotation.h"
+
+#define TAG "RotaryEncoderInputMapper_test"
+
+namespace android {
+
+using testing::AllOf;
+using testing::Return;
+using testing::VariantWith;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1};
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
+
+namespace {
+
+DisplayViewport createViewport() {
+    DisplayViewport v;
+    v.orientation = ui::Rotation::Rotation0;
+    v.logicalRight = DISPLAY_HEIGHT;
+    v.logicalBottom = DISPLAY_WIDTH;
+    v.physicalRight = DISPLAY_HEIGHT;
+    v.physicalBottom = DISPLAY_WIDTH;
+    v.deviceWidth = DISPLAY_HEIGHT;
+    v.deviceHeight = DISPLAY_WIDTH;
+    v.isActive = true;
+    return v;
+}
+
+DisplayViewport createPrimaryViewport() {
+    DisplayViewport v = createViewport();
+    v.displayId = DISPLAY_ID;
+    v.uniqueId = "local:1";
+    return v;
+}
+
+DisplayViewport createSecondaryViewport() {
+    DisplayViewport v = createViewport();
+    v.displayId = SECONDARY_DISPLAY_ID;
+    v.uniqueId = "local:2";
+    v.type = ViewportType::EXTERNAL;
+    return v;
+}
+
+/**
+ * A fake InputDeviceContext that allows the associated viewport to be specified for the mapper.
+ *
+ * This is currently necessary because InputMapperUnitTest doesn't register the mappers it creates
+ * with the InputDevice object, meaning that InputDevice::isIgnored becomes true, and the input
+ * device doesn't set its associated viewport when it's configured.
+ *
+ * TODO(b/319217713): work out a way to avoid this fake.
+ */
+class ViewportFakingInputDeviceContext : public InputDeviceContext {
+public:
+    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId,
+                                     std::optional<DisplayViewport> viewport)
+          : InputDeviceContext(device, eventHubId), mAssociatedViewport(viewport) {}
+
+    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId)
+          : ViewportFakingInputDeviceContext(device, eventHubId, createPrimaryViewport()) {}
+
+    std::optional<DisplayViewport> getAssociatedViewport() const override {
+        return mAssociatedViewport;
+    }
+
+    void setViewport(const std::optional<DisplayViewport>& viewport) {
+        mAssociatedViewport = viewport;
+    }
+
+private:
+    std::optional<DisplayViewport> mAssociatedViewport;
+};
+
+} // namespace
+
+/**
+ * Unit tests for RotaryEncoderInputMapper.
+ */
+class RotaryEncoderInputMapperTest : public InputMapperUnitTest {
+protected:
+    void SetUp() override { SetUpWithBus(BUS_USB); }
+    void SetUpWithBus(int bus) override {
+        InputMapperUnitTest::SetUpWithBus(bus);
+
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL))
+                .WillRepeatedly(Return(true));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
+                .WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+                .WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+                .WillRepeatedly(Return(false));
+    }
+};
+
+TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdWithAssociatedViewport) {
+    DisplayViewport primaryViewport = createPrimaryViewport();
+    DisplayViewport secondaryViewport = createSecondaryViewport();
+    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
+
+    // Set up the secondary display as the associated viewport of the mapper.
+    createDevice();
+    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(deviceContext, mReaderConfiguration);
+
+    std::list<NotifyArgs> args;
+    // Ensure input events are generated for the secondary display.
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                              WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
+                              WithDisplayId(SECONDARY_DISPLAY_ID)))));
+}
+
+TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdNoAssociatedViewport) {
+    // Set up the default display.
+    mFakePolicy->clearViewports();
+    mFakePolicy->addDisplayViewport(createPrimaryViewport());
+
+    // Set up the mapper with no associated viewport.
+    createDevice();
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
+
+    // Ensure input events are generated without display ID
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                              WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
+                              WithDisplayId(ui::LogicalDisplayId::INVALID)))));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/tests/TestConstants.h b/services/inputflinger/tests/TestConstants.h
index ad48b0f..082bbb8 100644
--- a/services/inputflinger/tests/TestConstants.h
+++ b/services/inputflinger/tests/TestConstants.h
@@ -24,6 +24,9 @@
 
 using std::chrono_literals::operator""ms;
 
+// Timeout for waiting for an input device to be added and processed
+static constexpr std::chrono::duration ADD_INPUT_DEVICE_TIMEOUT = 500ms;
+
 // Timeout for waiting for an expected event
 static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms;
 
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index 65fb9c6..f643fb1 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -697,6 +697,15 @@
     return argDistance == distance;
 }
 
+MATCHER_P2(WithScroll, scrollX, scrollY, "InputEvent with specified scroll values") {
+    const auto argScrollX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_HSCROLL);
+    const auto argScrollY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_VSCROLL);
+    *result_listener << "expected scroll values " << scrollX << " scroll x " << scrollY
+                     << " scroll y, but got " << argScrollX << " scroll x " << argScrollY
+                     << " scroll y";
+    return argScrollX == scrollX && argScrollY == scrollY;
+}
+
 MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") {
     const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
     const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
index 2b62dd1..1afb4f0 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -103,11 +103,8 @@
         setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
         setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
 
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, testing::_))
-                .WillRepeatedly([](int32_t eventHubId, int32_t, int32_t* outValue) {
-                    *outValue = 0;
-                    return OK;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
+                .WillRepeatedly(Return(0));
         EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, testing::_, testing::_))
                 .WillRepeatedly([]() -> base::Result<std::vector<int32_t>> {
                     return base::ResultError("Axis not supported", NAME_NOT_FOUND);
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index ff425dd..969c032 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -17,6 +17,7 @@
 
 #include <map>
 #include <memory>
+#include <optional>
 
 #include <EventHub.h>
 #include <InputDevice.h>
@@ -119,16 +120,25 @@
     void setAbsoluteAxisInfo(int32_t deviceId, int axis, const RawAbsoluteAxisInfo& axisInfo) {
         mAxes[deviceId][axis] = axisInfo;
     }
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override {
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                           int axis) const override {
         if (auto deviceAxesIt = mAxes.find(deviceId); deviceAxesIt != mAxes.end()) {
             const std::map<int, RawAbsoluteAxisInfo>& deviceAxes = deviceAxesIt->second;
             if (auto axisInfoIt = deviceAxes.find(axis); axisInfoIt != deviceAxes.end()) {
-                *outAxisInfo = axisInfoIt->second;
-                return OK;
+                return axisInfoIt->second;
             }
         }
-        return mFdp->ConsumeIntegral<status_t>();
+        if (mFdp->ConsumeBool()) {
+            return std::optional<RawAbsoluteAxisInfo>({
+                    .minValue = mFdp->ConsumeIntegral<int32_t>(),
+                    .maxValue = mFdp->ConsumeIntegral<int32_t>(),
+                    .flat = mFdp->ConsumeIntegral<int32_t>(),
+                    .fuzz = mFdp->ConsumeIntegral<int32_t>(),
+                    .resolution = mFdp->ConsumeIntegral<int32_t>(),
+            });
+        } else {
+            return std::nullopt;
+        }
     }
     bool hasRelativeAxis(int32_t deviceId, int axis) const override { return mFdp->ConsumeBool(); }
     bool hasInputProperty(int32_t deviceId, int property) const override {
@@ -197,9 +207,12 @@
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override {
         return mFdp->ConsumeIntegral<int32_t>();
     }
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                  int32_t* outValue) const override {
-        return mFdp->ConsumeIntegral<status_t>();
+    std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const override {
+        if (mFdp->ConsumeBool()) {
+            return mFdp->ConsumeIntegral<int32_t>();
+        } else {
+            return std::nullopt;
+        }
     }
     base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
                                                        size_t slotCount) const override {
diff --git a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
index c620032..ebbb311 100644
--- a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
@@ -34,7 +34,6 @@
     if (fdp.ConsumeBool()) {
         eventHub.setAbsoluteAxisInfo(id, axis,
                                      RawAbsoluteAxisInfo{
-                                             .valid = fdp.ConsumeBool(),
                                              .minValue = fdp.ConsumeIntegral<int32_t>(),
                                              .maxValue = fdp.ConsumeIntegral<int32_t>(),
                                              .flat = fdp.ConsumeIntegral<int32_t>(),
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
index f6f104e..b2dc89b 100644
--- a/services/sensorservice/aidl/fuzzer/Android.bp
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -26,6 +26,11 @@
         "libfakeservicemanager",
         "libcutils",
         "liblog",
+        "libsensor_flags_c_lib",
+    ],
+    shared_libs: [
+        "libaconfig_storage_read_api_cc",
+        "server_configurable_flags",
     ],
     srcs: [
         "fuzzer.cpp",
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 1b6c598..a37433c 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -85,7 +85,7 @@
         "libui",
         "libutils",
         "libSurfaceFlingerProp",
-        "libaconfig_storage_read_api_cc"
+        "libaconfig_storage_read_api_cc",
     ],
     static_libs: [
         "iinputflinger_aidl_lib_static",
@@ -187,6 +187,7 @@
         "FrameTracker.cpp",
         "HdrLayerInfoReporter.cpp",
         "HdrSdrRatioOverlay.cpp",
+        "Jank/JankTracker.cpp",
         "WindowInfosListenerInvoker.cpp",
         "Layer.cpp",
         "LayerFE.cpp",
diff --git a/services/surfaceflinger/ClientCache.cpp b/services/surfaceflinger/ClientCache.cpp
index 09e41ff..40ea8d3 100644
--- a/services/surfaceflinger/ClientCache.cpp
+++ b/services/surfaceflinger/ClientCache.cpp
@@ -22,7 +22,7 @@
 #include <cinttypes>
 
 #include <android-base/stringprintf.h>
-#include <gui/TraceUtils.h>
+#include <common/trace.h>
 #include <renderengine/impl/ExternalTexture.h>
 
 #include "ClientCache.h"
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 11759b8..d1429a2 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -35,6 +35,7 @@
 #pragma clang diagnostic ignored "-Wextra"
 
 #include <gui/BufferQueue.h>
+#include <ui/EdgeExtensionEffect.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/GraphicTypes.h>
 #include <ui/StretchEffect.h>
@@ -133,12 +134,16 @@
     // The bounds of the layer in layer local coordinates
     FloatRect geomLayerBounds;
 
+    // The crop to apply to the layer in layer local coordinates
+    FloatRect geomLayerCrop;
+
     ShadowSettings shadowSettings;
 
     // List of regions that require blur
     std::vector<BlurRegion> blurRegions;
 
     StretchEffect stretchEffect;
+    EdgeExtensionEffect edgeExtensionEffect;
 
     /*
      * Geometry state
diff --git a/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
index bdaa1d0..d9018bc 100644
--- a/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
@@ -37,7 +37,8 @@
             lhs.colorTransform == rhs.colorTransform &&
             lhs.disableBlending == rhs.disableBlending && lhs.shadow == rhs.shadow &&
             lhs.backgroundBlurRadius == rhs.backgroundBlurRadius &&
-            lhs.stretchEffect == rhs.stretchEffect;
+            lhs.stretchEffect == rhs.stretchEffect &&
+            lhs.edgeExtensionEffect == rhs.edgeExtensionEffect;
 }
 
 inline bool equalIgnoringBuffer(const renderengine::Buffer& lhs, const renderengine::Buffer& rhs) {
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index 4c77687..5c5d0cd 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <common/trace.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/LayerFE.h>
 #include <compositionengine/LayerFECompositionState.h>
@@ -23,7 +24,6 @@
 #include <ui/DisplayMap.h>
 
 #include <renderengine/RenderEngine.h>
-#include <utils/Trace.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -128,7 +128,7 @@
 } // namespace
 
 void CompositionEngine::present(CompositionRefreshArgs& args) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     preComposition(args);
@@ -155,7 +155,7 @@
     }
 
     {
-        ATRACE_NAME("Waiting on HWC");
+        SFTRACE_NAME("Waiting on HWC");
         for (auto& future : presentFutures) {
             // TODO(b/185536303): Call ftl::Future::wait() once it exists, since
             // we do not need the return value of get().
@@ -177,7 +177,7 @@
 }
 
 void CompositionEngine::preComposition(CompositionRefreshArgs& args) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     bool needsAnotherUpdate = false;
@@ -199,7 +199,7 @@
 // promises for buffer releases are fulfilled at the end of composition.
 void CompositionEngine::postComposition(CompositionRefreshArgs& args) {
     if (FlagManager::getInstance().ce_fence_promise()) {
-        ATRACE_CALL();
+        SFTRACE_CALL();
         ALOGV(__FUNCTION__);
 
         for (auto& layerFE : args.layers) {
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index c1617d7..77b1940 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/DisplayCreationArgs.h>
@@ -25,9 +26,6 @@
 #include <compositionengine/impl/DumpHelpers.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/RenderSurface.h>
-#include <gui/TraceUtils.h>
-
-#include <utils/Trace.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -235,7 +233,7 @@
 
 bool Display::chooseCompositionStrategy(
         std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
-    ATRACE_FORMAT("%s for %s", __func__, getNamePlusId().c_str());
+    SFTRACE_FORMAT("%s for %s", __func__, getNamePlusId().c_str());
     ALOGV(__FUNCTION__);
 
     if (mIsDisconnected) {
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 5b9a102..64cded8 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -17,6 +17,7 @@
 #include <SurfaceFlingerProperties.sysprop.h>
 #include <android-base/stringprintf.h>
 #include <common/FlagManager.h>
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/DisplayColorProfile.h>
@@ -31,7 +32,6 @@
 #include <compositionengine/impl/planner/Planner.h>
 #include <ftl/algorithm.h>
 #include <ftl/future.h>
-#include <gui/TraceUtils.h>
 #include <scheduler/FrameTargeter.h>
 #include <scheduler/Time.h>
 
@@ -53,7 +53,6 @@
 #include <android-base/properties.h>
 #include <ui/DebugUtils.h>
 #include <ui/HdrCapabilities.h>
-#include <utils/Trace.h>
 
 #include "TracedOrdinal.h"
 
@@ -424,7 +423,7 @@
 
 void Output::prepare(const compositionengine::CompositionRefreshArgs& refreshArgs,
                      LayerFESet& geomSnapshots) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     rebuildLayerStacks(refreshArgs, geomSnapshots);
@@ -453,8 +452,8 @@
                 })
                 .value();
     };
-    ATRACE_FORMAT("%s for %s%s", __func__, mNamePlusId.c_str(),
-                  stringifyExpectedPresentTime().c_str());
+    SFTRACE_FORMAT("%s for %s%s", __func__, mNamePlusId.c_str(),
+                   stringifyExpectedPresentTime().c_str());
     ALOGV(__FUNCTION__);
 
     updateColorProfile(refreshArgs);
@@ -518,7 +517,7 @@
     if (!outputState.isEnabled || CC_LIKELY(!refreshArgs.updatingOutputGeometryThisFrame)) {
         return;
     }
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     // Process the layers to determine visibility and coverage
@@ -804,7 +803,7 @@
 }
 
 void Output::updateCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     if (!getState().isEnabled) {
@@ -831,14 +830,14 @@
         return;
     }
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     mPlanner->plan(getOutputLayersOrderedByZ());
 }
 
 void Output::writeCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     if (!getState().isEnabled) {
@@ -1081,7 +1080,7 @@
 }
 
 void Output::prepareFrame() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     auto& outputState = editState();
@@ -1102,7 +1101,7 @@
 }
 
 ftl::Future<std::monostate> Output::presentFrameAndReleaseLayersAsync(bool flushEvenWhenDisabled) {
-    return ftl::Future<bool>(std::move(mHwComposerAsyncWorker->send([&]() {
+    return ftl::Future<bool>(std::move(mHwComposerAsyncWorker->send([this, flushEvenWhenDisabled]() {
                presentFrameAndReleaseLayers(flushEvenWhenDisabled);
                return true;
            })))
@@ -1116,7 +1115,7 @@
 }
 
 GpuCompositionResult Output::prepareFrameAsync() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
     auto& state = editState();
     const auto& previousChanges = state.previousDeviceRequestedChanges;
@@ -1146,7 +1145,7 @@
     state.strategyPrediction = predictionSucceeded ? CompositionStrategyPredictionState::SUCCESS
                                                    : CompositionStrategyPredictionState::FAIL;
     if (!predictionSucceeded) {
-        ATRACE_NAME("CompositionStrategyPredictionMiss");
+        SFTRACE_NAME("CompositionStrategyPredictionMiss");
         resetCompositionStrategy();
         if (chooseCompositionSuccess) {
             applyCompositionStrategy(changes);
@@ -1155,7 +1154,7 @@
         // Track the dequeued buffer to reuse so we don't need to dequeue another one.
         compositionResult.buffer = buffer;
     } else {
-        ATRACE_NAME("CompositionStrategyPredictionHit");
+        SFTRACE_NAME("CompositionStrategyPredictionHit");
     }
     state.previousDeviceRequestedChanges = std::move(changes);
     state.previousDeviceRequestedSuccess = chooseCompositionSuccess;
@@ -1187,7 +1186,7 @@
 }
 
 void Output::finishFrame(GpuCompositionResult&& result) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
     const auto& outputState = getState();
     if (!outputState.isEnabled) {
@@ -1276,7 +1275,7 @@
 std::optional<base::unique_fd> Output::composeSurfaces(
         const Region& debugRegion, std::shared_ptr<renderengine::ExternalTexture> tex,
         base::unique_fd& fd) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     const auto& outputState = getState();
@@ -1317,13 +1316,13 @@
         if (mClientCompositionRequestCache->exists(tex->getBuffer()->getId(),
                                                    clientCompositionDisplay,
                                                    clientCompositionLayers)) {
-            ATRACE_NAME("ClientCompositionCacheHit");
+            SFTRACE_NAME("ClientCompositionCacheHit");
             outputCompositionState.reusedClientComposition = true;
             setExpensiveRenderingExpected(false);
             // b/239944175 pass the fence associated with the buffer.
             return base::unique_fd(std::move(fd));
         }
-        ATRACE_NAME("ClientCompositionCacheMiss");
+        SFTRACE_NAME("ClientCompositionCacheMiss");
         mClientCompositionRequestCache->add(tex->getBuffer()->getId(), clientCompositionDisplay,
                                             clientCompositionLayers);
     }
@@ -1570,7 +1569,7 @@
 }
 
 void Output::presentFrameAndReleaseLayers(bool flushEvenWhenDisabled) {
-    ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
+    SFTRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
     ALOGV(__FUNCTION__);
 
     if (!getState().isEnabled) {
diff --git a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
index c0b23d9..d6028bf 100644
--- a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
@@ -18,6 +18,7 @@
 
 #include <android-base/stringprintf.h>
 #include <android/native_window.h>
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/DisplaySurface.h>
@@ -32,7 +33,6 @@
 #include <system/window.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/Rect.h>
-#include <utils/Trace.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -149,7 +149,7 @@
 
 std::shared_ptr<renderengine::ExternalTexture> RenderSurface::dequeueBuffer(
         base::unique_fd* bufferFence) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     int fd = -1;
     ANativeWindowBuffer* buffer = nullptr;
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index ea9442d..409a206 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -21,6 +21,7 @@
 
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/planner/CachedSet.h>
 #include <math/HashCombine.h>
@@ -28,7 +29,6 @@
 #include <renderengine/RenderEngine.h>
 #include <ui/DebugUtils.h>
 #include <ui/HdrRenderTypeUtils.h>
-#include <utils/Trace.h>
 
 namespace android::compositionengine::impl::planner {
 
@@ -160,7 +160,7 @@
 void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& texturePool,
                        const OutputCompositionState& outputState,
                        bool deviceHandlesColorTransform) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (outputState.powerCallback) {
         outputState.powerCallback->notifyCpuLoadUp();
     }
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 4bafed2..783209c 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -21,11 +21,10 @@
 
 #include <android-base/properties.h>
 #include <common/FlagManager.h>
+#include <common/trace.h>
 #include <compositionengine/impl/planner/Flattener.h>
 #include <compositionengine/impl/planner/LayerState.h>
 
-#include <gui/TraceUtils.h>
-
 using time_point = std::chrono::steady_clock::time_point;
 using namespace std::chrono_literals;
 
@@ -77,7 +76,7 @@
 
 NonBufferHash Flattener::flattenLayers(const std::vector<const LayerState*>& layers,
                                        NonBufferHash hash, time_point now) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     const size_t unflattenedDisplayCost = calculateDisplayCost(layers);
     mUnflattenedDisplayCost += unflattenedDisplayCost;
 
@@ -113,7 +112,7 @@
         const OutputCompositionState& outputState,
         std::optional<std::chrono::steady_clock::time_point> renderDeadline,
         bool deviceHandlesColorTransform) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!mNewCachedSet) {
         return;
@@ -121,7 +120,7 @@
 
     // Ensure that a cached set has a valid buffer first
     if (mNewCachedSet->hasRenderedBuffer()) {
-        ATRACE_NAME("mNewCachedSet->hasRenderedBuffer()");
+        SFTRACE_NAME("mNewCachedSet->hasRenderedBuffer()");
         return;
     }
 
@@ -138,13 +137,13 @@
 
             if (mNewCachedSet->getSkipCount() <=
                 mTunables.mRenderScheduling->maxDeferRenderAttempts) {
-                ATRACE_FORMAT("DeadlinePassed: exceeded deadline by: %d us",
-                              std::chrono::duration_cast<std::chrono::microseconds>(
-                                      estimatedRenderFinish - *renderDeadline)
-                                      .count());
+                SFTRACE_FORMAT("DeadlinePassed: exceeded deadline by: %d us",
+                               std::chrono::duration_cast<std::chrono::microseconds>(
+                                       estimatedRenderFinish - *renderDeadline)
+                                       .count());
                 return;
             } else {
-                ATRACE_NAME("DeadlinePassed: exceeded max skips");
+                SFTRACE_NAME("DeadlinePassed: exceeded max skips");
             }
         }
     }
@@ -271,7 +270,7 @@
 // was already populated with these layers, i.e. on the second and following
 // calls with the same geometry.
 bool Flattener::mergeWithCachedSets(const std::vector<const LayerState*>& layers, time_point now) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::vector<CachedSet> merged;
 
     if (mLayers.empty()) {
@@ -415,7 +414,7 @@
 }
 
 std::vector<Flattener::Run> Flattener::findCandidateRuns(time_point now) const {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::vector<Run> runs;
     bool isPartOfRun = false;
     Run::Builder builder;
@@ -431,8 +430,8 @@
         if (!layerIsInactive && currentSet->getLayerCount() == kNumLayersFpsConsideration) {
             auto layerFps = currentSet->getFirstLayer().getState()->getFps();
             if (layerFps > 0 && layerFps <= kFpsActiveThreshold) {
-                ATRACE_FORMAT("layer is considered inactive due to low FPS [%s] %f",
-                              currentSet->getFirstLayer().getName().c_str(), layerFps);
+                SFTRACE_FORMAT("layer is considered inactive due to low FPS [%s] %f",
+                               currentSet->getFirstLayer().getName().c_str(), layerFps);
                 layerIsInactive = true;
             }
         }
@@ -494,7 +493,7 @@
 }
 
 void Flattener::buildCachedSets(time_point now) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mLayers.empty()) {
         ALOGV("[%s] No layers found, returning", __func__);
         return;
@@ -508,7 +507,7 @@
     for (const CachedSet& layer : mLayers) {
         // TODO (b/191997217): make it less aggressive, and sync with findCandidateRuns
         if (layer.hasProtectedLayers()) {
-            ATRACE_NAME("layer->hasProtectedLayers()");
+            SFTRACE_NAME("layer->hasProtectedLayers()");
             return;
         }
     }
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
index 5e6cade..d114ff7 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -21,11 +21,11 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <android-base/properties.h>
+#include <common/trace.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <compositionengine/impl/planner/Planner.h>
 
-#include <utils/Trace.h>
 #include <chrono>
 
 namespace android::compositionengine::impl::planner {
@@ -83,7 +83,7 @@
 
 void Planner::plan(
         compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::unordered_set<LayerId> removedLayers;
     removedLayers.reserve(mPreviousLayers.size());
 
@@ -165,7 +165,7 @@
 
 void Planner::reportFinalPlan(
         compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (!mPredictorEnabled) {
         return;
     }
@@ -204,7 +204,7 @@
 void Planner::renderCachedSets(const OutputCompositionState& outputState,
                                std::optional<std::chrono::steady_clock::time_point> renderDeadline,
                                bool deviceHandlesColorTransform) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mFlattener.renderCachedSets(outputState, renderDeadline, deviceHandlesColorTransform);
 }
 
diff --git a/services/surfaceflinger/Display/DisplayModeController.cpp b/services/surfaceflinger/Display/DisplayModeController.cpp
index a6a9bec..c43d585 100644
--- a/services/surfaceflinger/Display/DisplayModeController.cpp
+++ b/services/surfaceflinger/Display/DisplayModeController.cpp
@@ -83,7 +83,7 @@
             FTL_EXPECT(mDisplays.get(displayId).ok_or(DesiredModeAction::None)).get();
 
     {
-        ATRACE_NAME(displayPtr->concatId(__func__).c_str());
+        SFTRACE_NAME(displayPtr->concatId(__func__).c_str());
         ALOGD("%s %s", displayPtr->concatId(__func__).c_str(), to_string(desiredMode).c_str());
 
         std::scoped_lock lock(displayPtr->desiredModeLock);
@@ -204,7 +204,7 @@
         return false;
     }
 
-    ATRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
+    SFTRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
     return true;
 }
 
@@ -227,8 +227,8 @@
                                                 Fps vsyncRate, Fps renderFps) {
     const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();
 
-    ATRACE_INT(displayPtr->activeModeFpsTrace.c_str(), vsyncRate.getIntValue());
-    ATRACE_INT(displayPtr->renderRateFpsTrace.c_str(), renderFps.getIntValue());
+    SFTRACE_INT(displayPtr->activeModeFpsTrace.c_str(), vsyncRate.getIntValue());
+    SFTRACE_INT(displayPtr->renderRateFpsTrace.c_str(), renderFps.getIntValue());
 
     displayPtr->selectorPtr->setActiveMode(modeId, renderFps);
 
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 27ea4a9..8288b99 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -24,6 +24,7 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/DisplayColorProfile.h>
@@ -398,7 +399,7 @@
 }
 
 void DisplayDevice::updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mHdrSdrRatio = currentHdrSdrRatio;
     if (mHdrSdrRatioOverlay) {
         mHdrSdrRatioOverlay->changeHdrSdrRatio(currentHdrSdrRatio);
@@ -440,7 +441,7 @@
 }
 
 void DisplayDevice::updateRefreshRateOverlayRate(Fps refreshRate, Fps renderFps, bool setByHwc) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mRefreshRateOverlay) {
         if (!mRefreshRateOverlay->isSetByHwc() || setByHwc) {
             if (mRefreshRateSelector->isVrrDevice() && !mRefreshRateOverlay->isSetByHwc()) {
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 362ab9c..d50a0bc 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -25,9 +25,8 @@
 #include <android/binder_ibinder_platform.h>
 #include <android/binder_manager.h>
 #include <common/FlagManager.h>
-#include <gui/TraceUtils.h>
+#include <common/trace.h>
 #include <log/log.h>
-#include <utils/Trace.h>
 
 #include <aidl/android/hardware/graphics/composer3/BnComposerCallback.h>
 
@@ -677,7 +676,7 @@
 
 Error AidlComposer::presentDisplay(Display display, int* outPresentFence) {
     const auto displayId = translate<int64_t>(display);
-    ATRACE_FORMAT("HwcPresentDisplay %" PRId64, displayId);
+    SFTRACE_FORMAT("HwcPresentDisplay %" PRId64, displayId);
 
     Error error = Error::NONE;
     mMutex.lock_shared();
@@ -810,7 +809,7 @@
                                     int32_t frameIntervalNs, uint32_t* outNumTypes,
                                     uint32_t* outNumRequests) {
     const auto displayId = translate<int64_t>(display);
-    ATRACE_FORMAT("HwcValidateDisplay %" PRId64, displayId);
+    SFTRACE_FORMAT("HwcValidateDisplay %" PRId64, displayId);
 
     Error error = Error::NONE;
     mMutex.lock_shared();
@@ -840,7 +839,7 @@
                                              uint32_t* outNumRequests, int* outPresentFence,
                                              uint32_t* state) {
     const auto displayId = translate<int64_t>(display);
-    ATRACE_FORMAT("HwcPresentOrValidateDisplay %" PRId64, displayId);
+    SFTRACE_FORMAT("HwcPresentOrValidateDisplay %" PRId64, displayId);
 
     Error error = Error::NONE;
     mMutex.lock_shared();
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 3d285a8..73fa855 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -28,16 +28,15 @@
 #include "HWComposer.h"
 
 #include <android-base/properties.h>
+#include <common/trace.h>
 #include <compositionengine/Output.h>
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <ftl/concat.h>
-#include <gui/TraceUtils.h>
 #include <log/log.h>
 #include <ui/DebugUtils.h>
 #include <ui/GraphicBuffer.h>
 #include <utils/Errors.h>
-#include <utils/Trace.h>
 
 #include "../Layer.h" // needed only for debugging
 #include "../SurfaceFlingerProperties.h"
@@ -178,8 +177,8 @@
         displayData.lastPresentTimestamp = timestamp;
     }
 
-    ATRACE_INT(ftl::Concat("HW_VSYNC_", displayIdOpt->value).c_str(),
-               displayData.vsyncTraceToggle);
+    SFTRACE_INT(ftl::Concat("HW_VSYNC_", displayIdOpt->value).c_str(),
+                displayData.vsyncTraceToggle);
     displayData.vsyncTraceToggle = !displayData.vsyncTraceToggle;
 
     return displayIdOpt;
@@ -428,14 +427,14 @@
         return;
     }
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     auto error = displayData.hwcDisplay->setVsyncEnabled(enabled);
     RETURN_IF_HWC_ERROR(error, displayId);
 
     displayData.vsyncEnabled = enabled;
 
-    ATRACE_INT(ftl::Concat("HW_VSYNC_ON_", displayId.value).c_str(),
-               enabled == hal::Vsync::ENABLE ? 1 : 0);
+    SFTRACE_INT(ftl::Concat("HW_VSYNC_ON_", displayId.value).c_str(),
+                enabled == hal::Vsync::ENABLE ? 1 : 0);
 }
 
 status_t HWComposer::setClientTarget(HalDisplayId displayId, uint32_t slot,
@@ -455,7 +454,7 @@
         std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
         nsecs_t expectedPresentTime, Fps frameInterval,
         std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
@@ -493,7 +492,7 @@
     }();
 
     displayData.validateWasSkipped = false;
-    ATRACE_FORMAT("NextFrameInterval %d_Hz", frameInterval.getIntValue());
+    SFTRACE_FORMAT("NextFrameInterval %d_Hz", frameInterval.getIntValue());
     if (canSkipValidate) {
         sp<Fence> outPresentFence = Fence::NO_FENCE;
         uint32_t state = UINT32_MAX;
@@ -568,7 +567,7 @@
 status_t HWComposer::presentAndGetReleaseFences(
         HalDisplayId displayId,
         std::optional<std::chrono::steady_clock::time_point> earliestPresentTime) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
@@ -584,7 +583,7 @@
     }
 
     if (earliestPresentTime) {
-        ATRACE_NAME("wait for earliest present time");
+        SFTRACE_NAME("wait for earliest present time");
         std::this_thread::sleep_until(*earliestPresentTime);
     }
 
@@ -897,9 +896,9 @@
 status_t HWComposer::notifyExpectedPresent(PhysicalDisplayId displayId,
                                            TimePoint expectedPresentTime, Fps frameInterval) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
-    ATRACE_FORMAT("%s ExpectedPresentTime in %.2fms frameInterval %.2fms", __func__,
-                  ticks<std::milli, float>(expectedPresentTime - TimePoint::now()),
-                  ticks<std::milli, float>(Duration::fromNs(frameInterval.getPeriodNsecs())));
+    SFTRACE_FORMAT("%s ExpectedPresentTime in %.2fms frameInterval %.2fms", __func__,
+                   ticks<std::milli, float>(expectedPresentTime - TimePoint::now()),
+                   ticks<std::milli, float>(Duration::fromNs(frameInterval.getPeriodNsecs())));
     const auto error = mComposer->notifyExpectedPresent(mDisplayData[displayId].hwcDisplay->getId(),
                                                         expectedPresentTime.ns(),
                                                         frameInterval.getPeriodNsecs());
@@ -1149,7 +1148,7 @@
 
 status_t HWComposer::setIdleTimerEnabled(PhysicalDisplayId displayId,
                                          std::chrono::milliseconds timeout) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
     const auto error = mDisplayData[displayId].hwcDisplay->setIdleTimerEnabled(timeout);
     if (error == hal::Error::UNSUPPORTED) {
@@ -1168,7 +1167,7 @@
 }
 
 Hwc2::AidlTransform HWComposer::getPhysicalDisplayOrientation(PhysicalDisplayId displayId) const {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     RETURN_IF_INVALID_DISPLAY(displayId, Hwc2::AidlTransform::NONE);
     Hwc2::AidlTransform outTransform;
     const auto& hwcDisplay = mDisplayData.at(displayId).hwcDisplay;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index 12ab2c2..c5008d8 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -27,11 +27,11 @@
 #include <SurfaceFlingerProperties.h>
 #include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
 #include <android/binder_manager.h>
+#include <common/trace.h>
 #include <composer-command-buffer/2.2/ComposerCommandBuffer.h>
 #include <hidl/HidlTransportSupport.h>
 #include <hidl/HidlTransportUtils.h>
 #include <log/log.h>
-#include <utils/Trace.h>
 
 #include "HWC2.h"
 #include "Hal.h"
@@ -588,7 +588,7 @@
 }
 
 Error HidlComposer::presentDisplay(Display display, int* outPresentFence) {
-    ATRACE_NAME("HwcPresentDisplay");
+    SFTRACE_NAME("HwcPresentDisplay");
     mWriter.selectDisplay(display);
     mWriter.presentDisplay();
 
@@ -676,7 +676,7 @@
 Error HidlComposer::validateDisplay(Display display, nsecs_t /*expectedPresentTime*/,
                                     int32_t /*frameIntervalNs*/, uint32_t* outNumTypes,
                                     uint32_t* outNumRequests) {
-    ATRACE_NAME("HwcValidateDisplay");
+    SFTRACE_NAME("HwcValidateDisplay");
     mWriter.selectDisplay(display);
     mWriter.validateDisplay();
 
@@ -694,7 +694,7 @@
                                              int32_t /*frameIntervalNs*/, uint32_t* outNumTypes,
                                              uint32_t* outNumRequests, int* outPresentFence,
                                              uint32_t* state) {
-    ATRACE_NAME("HwcPresentOrValidateDisplay");
+    SFTRACE_NAME("HwcPresentOrValidateDisplay");
     mWriter.selectDisplay(display);
     mWriter.presentOrvalidateDisplay();
 
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index 6c1a813..da56014 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -27,9 +27,9 @@
 #include <optional>
 
 #include <android-base/properties.h>
+#include <common/trace.h>
 #include <utils/Log.h>
 #include <utils/Mutex.h>
-#include <utils/Trace.h>
 
 #include <binder/IServiceManager.h>
 
@@ -74,9 +74,9 @@
 
 void traceExpensiveRendering(bool enabled) {
     if (enabled) {
-        ATRACE_ASYNC_BEGIN("ExpensiveRendering", 0);
+        SFTRACE_ASYNC_BEGIN("ExpensiveRendering", 0);
     } else {
-        ATRACE_ASYNC_END("ExpensiveRendering", 0);
+        SFTRACE_ASYNC_END("ExpensiveRendering", 0);
     }
 }
 
@@ -210,7 +210,7 @@
         ALOGV("Power hint session is not enabled, skip sending session hint");
         return;
     }
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (sTraceHintSessionData) ATRACE_INT("Session hint", static_cast<int>(hint));
     {
         std::scoped_lock lock(mHintSessionMutex);
@@ -295,7 +295,7 @@
         ALOGV("Power hint session is not enabled, skipping target update");
         return;
     }
-    ATRACE_CALL();
+    SFTRACE_CALL();
     {
         mTargetDuration = targetDuration;
         if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration.ns());
@@ -324,7 +324,7 @@
         ALOGV("Actual work duration power hint cannot be sent, skipping");
         return;
     }
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::optional<WorkDuration> actualDuration = estimateWorkDuration();
     if (!actualDuration.has_value() || actualDuration->durationNanos < 0) {
         ALOGV("Failed to send actual work duration, skipping");
@@ -332,16 +332,16 @@
     }
     actualDuration->durationNanos += sTargetSafetyMargin.ns();
     if (sTraceHintSessionData) {
-        ATRACE_INT64("Measured duration", actualDuration->durationNanos);
-        ATRACE_INT64("Target error term", actualDuration->durationNanos - mTargetDuration.ns());
-        ATRACE_INT64("Reported duration", actualDuration->durationNanos);
+        SFTRACE_INT64("Measured duration", actualDuration->durationNanos);
+        SFTRACE_INT64("Target error term", actualDuration->durationNanos - mTargetDuration.ns());
+        SFTRACE_INT64("Reported duration", actualDuration->durationNanos);
         if (supportsGpuReporting()) {
-            ATRACE_INT64("Reported cpu duration", actualDuration->cpuDurationNanos);
-            ATRACE_INT64("Reported gpu duration", actualDuration->gpuDurationNanos);
+            SFTRACE_INT64("Reported cpu duration", actualDuration->cpuDurationNanos);
+            SFTRACE_INT64("Reported gpu duration", actualDuration->gpuDurationNanos);
         }
-        ATRACE_INT64("Reported target", mLastTargetDurationSent.ns());
-        ATRACE_INT64("Reported target error term",
-                     actualDuration->durationNanos - mLastTargetDurationSent.ns());
+        SFTRACE_INT64("Reported target", mLastTargetDurationSent.ns());
+        SFTRACE_INT64("Reported target error term",
+                      actualDuration->durationNanos - mLastTargetDurationSent.ns());
     }
 
     ALOGV("Sending actual work duration of: %" PRId64 " with cpu: %" PRId64 " and gpu: %" PRId64
@@ -664,9 +664,9 @@
             .gpuDurationNanos = supportsGpuReporting() ? estimatedGpuDuration.ns() : 0,
     };
     if (sTraceHintSessionData) {
-        ATRACE_INT64("Idle duration", idleDuration.ns());
-        ATRACE_INT64("Total duration", totalDuration.ns());
-        ATRACE_INT64("Flinger duration", flingerDuration.ns());
+        SFTRACE_INT64("Idle duration", idleDuration.ns());
+        SFTRACE_INT64("Total duration", totalDuration.ns());
+        SFTRACE_INT64("Flinger duration", flingerDuration.ns());
     }
     return std::make_optional(duration);
 }
diff --git a/services/surfaceflinger/DisplayRenderArea.cpp b/services/surfaceflinger/DisplayRenderArea.cpp
index 55b395b..c63c738 100644
--- a/services/surfaceflinger/DisplayRenderArea.cpp
+++ b/services/surfaceflinger/DisplayRenderArea.cpp
@@ -22,22 +22,20 @@
 std::unique_ptr<RenderArea> DisplayRenderArea::create(wp<const DisplayDevice> displayWeak,
                                                       const Rect& sourceCrop, ui::Size reqSize,
                                                       ui::Dataspace reqDataSpace,
-                                                      bool hintForSeamlessTransition,
-                                                      bool allowSecureLayers) {
+                                                      ftl::Flags<Options> options) {
     if (auto display = displayWeak.promote()) {
         // Using new to access a private constructor.
-        return std::unique_ptr<DisplayRenderArea>(
-                new DisplayRenderArea(std::move(display), sourceCrop, reqSize, reqDataSpace,
-                                      hintForSeamlessTransition, allowSecureLayers));
+        return std::unique_ptr<DisplayRenderArea>(new DisplayRenderArea(std::move(display),
+                                                                        sourceCrop, reqSize,
+                                                                        reqDataSpace, options));
     }
     return nullptr;
 }
 
 DisplayRenderArea::DisplayRenderArea(sp<const DisplayDevice> display, const Rect& sourceCrop,
                                      ui::Size reqSize, ui::Dataspace reqDataSpace,
-                                     bool hintForSeamlessTransition, bool allowSecureLayers)
-      : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, hintForSeamlessTransition,
-                   allowSecureLayers),
+                                     ftl::Flags<Options> options)
+      : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, options),
         mDisplay(std::move(display)),
         mSourceCrop(sourceCrop) {}
 
@@ -46,7 +44,7 @@
 }
 
 bool DisplayRenderArea::isSecure() const {
-    return mAllowSecureLayers && mDisplay->isSecure();
+    return mOptions.test(Options::CAPTURE_SECURE_LAYERS) && mDisplay->isSecure();
 }
 
 sp<const DisplayDevice> DisplayRenderArea::getDisplayDevice() const {
diff --git a/services/surfaceflinger/DisplayRenderArea.h b/services/surfaceflinger/DisplayRenderArea.h
index 4555a9e..677d019 100644
--- a/services/surfaceflinger/DisplayRenderArea.h
+++ b/services/surfaceflinger/DisplayRenderArea.h
@@ -29,8 +29,7 @@
 public:
     static std::unique_ptr<RenderArea> create(wp<const DisplayDevice>, const Rect& sourceCrop,
                                               ui::Size reqSize, ui::Dataspace,
-                                              bool hintForSeamlessTransition,
-                                              bool allowSecureLayers = true);
+                                              ftl::Flags<Options> options);
 
     const ui::Transform& getTransform() const override;
     bool isSecure() const override;
@@ -39,7 +38,7 @@
 
 private:
     DisplayRenderArea(sp<const DisplayDevice>, const Rect& sourceCrop, ui::Size reqSize,
-                      ui::Dataspace, bool hintForSeamlessTransition, bool allowSecureLayers = true);
+                      ui::Dataspace, ftl::Flags<Options> options);
 
     const sp<const DisplayDevice> mDisplay;
     const Rect mSourceCrop;
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 2596a25..2a0ee5a 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -22,14 +22,16 @@
 
 #include <android-base/stringprintf.h>
 #include <common/FlagManager.h>
+#include <common/trace.h>
 #include <utils/Log.h>
-#include <utils/Trace.h>
 
 #include <chrono>
 #include <cinttypes>
 #include <numeric>
 #include <unordered_set>
 
+#include "../Jank/JankTracker.h"
+
 namespace android::frametimeline {
 
 using base::StringAppendF;
@@ -357,7 +359,11 @@
 
 void SurfaceFrame::setAcquireFenceTime(nsecs_t acquireFenceTime) {
     std::scoped_lock lock(mMutex);
-    mActuals.endTime = std::max(acquireFenceTime, mActualQueueTime);
+    if (CC_UNLIKELY(acquireFenceTime == Fence::SIGNAL_TIME_PENDING)) {
+        mActuals.endTime = mActualQueueTime;
+    } else {
+        mActuals.endTime = std::max(acquireFenceTime, mActualQueueTime);
+    }
 }
 
 void SurfaceFrame::setDropTime(nsecs_t dropTime) {
@@ -685,6 +691,13 @@
         mTimeStats->incrementJankyFrames({refreshRate, mRenderRate, mOwnerUid, mLayerName,
                                           mGameMode, mJankType, displayDeadlineDelta,
                                           displayPresentDelta, deadlineDelta});
+
+        gui::JankData jd;
+        jd.frameVsyncId = mToken;
+        jd.jankType = mJankType;
+        jd.frameIntervalNs =
+                (mRenderRate ? *mRenderRate : mDisplayFrameRenderRate).getPeriodNsecs();
+        JankTracker::onJankData(mLayerId, jd);
     }
 }
 
@@ -820,7 +833,7 @@
 namespace impl {
 
 int64_t TokenManager::generateTokenForPredictions(TimelineItem&& predictions) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mMutex);
     while (mPredictions.size() >= kMaxTokens) {
         mPredictions.erase(mPredictions.begin());
@@ -866,7 +879,7 @@
 std::shared_ptr<SurfaceFrame> FrameTimeline::createSurfaceFrameForToken(
         const FrameTimelineInfo& frameTimelineInfo, pid_t ownerPid, uid_t ownerUid, int32_t layerId,
         std::string layerName, std::string debugName, bool isBuffer, GameMode gameMode) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (frameTimelineInfo.vsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) {
         return std::make_shared<SurfaceFrame>(frameTimelineInfo, ownerPid, ownerUid, layerId,
                                               std::move(layerName), std::move(debugName),
@@ -902,14 +915,14 @@
 }
 
 void FrameTimeline::addSurfaceFrame(std::shared_ptr<SurfaceFrame> surfaceFrame) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mMutex);
     mCurrentDisplayFrame->addSurfaceFrame(surfaceFrame);
 }
 
 void FrameTimeline::setSfWakeUp(int64_t token, nsecs_t wakeUpTime, Fps refreshRate,
                                 Fps renderRate) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mMutex);
     mCurrentDisplayFrame->onSfWakeUp(token, refreshRate, renderRate,
                                      mTokenManager.getPredictionsForToken(token), wakeUpTime);
@@ -918,7 +931,7 @@
 void FrameTimeline::setSfPresent(nsecs_t sfPresentTime,
                                  const std::shared_ptr<FenceTime>& presentFence,
                                  const std::shared_ptr<FenceTime>& gpuFence) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mMutex);
     mCurrentDisplayFrame->setActualEndTime(sfPresentTime);
     mCurrentDisplayFrame->setGpuFence(gpuFence);
@@ -928,7 +941,7 @@
 }
 
 void FrameTimeline::onCommitNotComposited() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mMutex);
     mCurrentDisplayFrame->onCommitNotComposited();
     mCurrentDisplayFrame.reset();
@@ -1507,7 +1520,7 @@
 }
 
 void FrameTimeline::parseArgs(const Vector<String16>& args, std::string& result) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::unordered_map<std::string, bool> argsMap;
     for (size_t i = 0; i < args.size(); i++) {
         argsMap[std::string(String8(args[i]).c_str())] = true;
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index 39a6b77..f4335f3 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -413,11 +413,11 @@
 
 void LayerHierarchyBuilder::update(LayerLifecycleManager& layerLifecycleManager) {
     if (!mInitialized) {
-        ATRACE_NAME("LayerHierarchyBuilder:init");
+        SFTRACE_NAME("LayerHierarchyBuilder:init");
         init(layerLifecycleManager.getLayers());
     } else if (layerLifecycleManager.getGlobalChanges().test(
                        RequestedLayerState::Changes::Hierarchy)) {
-        ATRACE_NAME("LayerHierarchyBuilder:update");
+        SFTRACE_NAME("LayerHierarchyBuilder:update");
         doUpdate(layerLifecycleManager.getLayers(), layerLifecycleManager.getDestroyedLayers());
     } else {
         return; // nothing to do
@@ -426,7 +426,7 @@
     uint32_t invalidRelativeRoot;
     bool hasRelZLoop = mRoot.hasRelZLoop(invalidRelativeRoot);
     while (hasRelZLoop) {
-        ATRACE_NAME("FixRelZLoop");
+        SFTRACE_NAME("FixRelZLoop");
         TransactionTraceWriter::getInstance().invoke("relz_loop_detected",
                                                      /*overwrite=*/false);
         layerLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 70e3c64..e5f6b7b 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -322,6 +322,10 @@
             << touchableRegion.bottom << "," << touchableRegion.right << "}"
             << "}";
     }
+
+    if (obj.edgeExtensionEffect.hasEffect()) {
+        out << obj.edgeExtensionEffect;
+    }
     return out;
 }
 
@@ -492,8 +496,10 @@
         requested.what &
                 (layer_state_t::eBufferChanged | layer_state_t::eDataspaceChanged |
                  layer_state_t::eApiChanged | layer_state_t::eShadowRadiusChanged |
-                 layer_state_t::eBlurRegionsChanged | layer_state_t::eStretchChanged)) {
-        forceClientComposition = shadowSettings.length > 0 || stretchEffect.hasEffect();
+                 layer_state_t::eBlurRegionsChanged | layer_state_t::eStretchChanged |
+                 layer_state_t::eEdgeExtensionChanged)) {
+        forceClientComposition = shadowSettings.length > 0 || stretchEffect.hasEffect() ||
+                edgeExtensionEffect.hasEffect();
     }
 
     if (forceUpdate ||
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index ca53a0d..4e09381 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -23,8 +23,8 @@
 #include <optional>
 
 #include <common/FlagManager.h>
+#include <common/trace.h>
 #include <ftl/small_map.h>
-#include <gui/TraceUtils.h>
 #include <ui/DisplayMap.h>
 #include <ui/FloatRect.h>
 
@@ -317,6 +317,21 @@
     }
 }
 
+void updateMetadataAndGameMode(LayerSnapshot& snapshot, const RequestedLayerState& requested,
+                               const LayerSnapshotBuilder::Args& args,
+                               const LayerSnapshot& parentSnapshot) {
+    if (snapshot.changes.test(RequestedLayerState::Changes::GameMode)) {
+        snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE)
+                ? requested.gameMode
+                : parentSnapshot.gameMode;
+    }
+    updateMetadata(snapshot, requested, args);
+    if (args.includeMetadata) {
+        snapshot.layerMetadata = parentSnapshot.layerMetadata;
+        snapshot.layerMetadata.merge(requested.metadata);
+    }
+}
+
 void clearChanges(LayerSnapshot& snapshot) {
     snapshot.changes.clear();
     snapshot.clientChanges = 0;
@@ -352,6 +367,7 @@
     snapshot.geomLayerBounds = getMaxDisplayBounds({});
     snapshot.roundedCorner = RoundedCornerState();
     snapshot.stretchEffect = {};
+    snapshot.edgeExtensionEffect = {};
     snapshot.outputFilter.layerStack = ui::DEFAULT_LAYER_STACK;
     snapshot.outputFilter.toInternalDisplay = false;
     snapshot.isSecure = false;
@@ -387,7 +403,7 @@
 
     // There are only content changes which do not require any child layer snapshots to be updated.
     ALOGV("%s", __func__);
-    ATRACE_NAME("FastPath");
+    SFTRACE_NAME("FastPath");
 
     uint32_t primaryDisplayRotationFlags = getPrimaryDisplayRotationFlags(args.displays);
     if (forceUpdate || args.displayChanges) {
@@ -421,7 +437,7 @@
 }
 
 void LayerSnapshotBuilder::updateSnapshots(const Args& args) {
-    ATRACE_NAME("UpdateSnapshots");
+    SFTRACE_NAME("UpdateSnapshots");
     LayerSnapshot rootSnapshot = args.rootSnapshot;
     if (args.parentCrop) {
         rootSnapshot.geomLayerBounds = *args.parentCrop;
@@ -762,6 +778,11 @@
                                  RequestedLayerState::Changes::Input)) {
             updateInput(snapshot, requested, parentSnapshot, path, args);
         }
+        if (forceUpdate ||
+            (args.includeMetadata &&
+             snapshot.changes.test(RequestedLayerState::Changes::Metadata))) {
+            updateMetadataAndGameMode(snapshot, requested, args, parentSnapshot);
+        }
         return;
     }
 
@@ -791,6 +812,32 @@
                 : parentSnapshot.stretchEffect;
     }
 
+    if (forceUpdate ||
+        (snapshot.clientChanges | parentSnapshot.clientChanges) &
+                layer_state_t::eEdgeExtensionChanged) {
+        if (requested.edgeExtensionParameters.extendLeft ||
+            requested.edgeExtensionParameters.extendRight ||
+            requested.edgeExtensionParameters.extendTop ||
+            requested.edgeExtensionParameters.extendBottom) {
+            // This is the root layer to which the extension is applied
+            snapshot.edgeExtensionEffect =
+                    EdgeExtensionEffect(requested.edgeExtensionParameters.extendLeft,
+                                        requested.edgeExtensionParameters.extendRight,
+                                        requested.edgeExtensionParameters.extendTop,
+                                        requested.edgeExtensionParameters.extendBottom);
+        } else if (parentSnapshot.clientChanges & layer_state_t::eEdgeExtensionChanged) {
+            // Extension is inherited
+            snapshot.edgeExtensionEffect = parentSnapshot.edgeExtensionEffect;
+        } else {
+            // There is no edge extension
+            snapshot.edgeExtensionEffect.reset();
+        }
+        if (snapshot.edgeExtensionEffect.hasEffect()) {
+            snapshot.clientChanges |= layer_state_t::eEdgeExtensionChanged;
+            snapshot.changes |= RequestedLayerState::Changes::Geometry;
+        }
+    }
+
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eColorTransformChanged) {
         if (!parentSnapshot.colorTransformIsIdentity) {
             snapshot.colorTransform = parentSnapshot.colorTransform * requested.colorTransform;
@@ -801,15 +848,8 @@
         }
     }
 
-    if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::GameMode)) {
-        snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE)
-                ? requested.gameMode
-                : parentSnapshot.gameMode;
-        updateMetadata(snapshot, requested, args);
-        if (args.includeMetadata) {
-            snapshot.layerMetadata = parentSnapshot.layerMetadata;
-            snapshot.layerMetadata.merge(requested.metadata);
-        }
+    if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::Metadata)) {
+        updateMetadataAndGameMode(snapshot, requested, args, parentSnapshot);
     }
 
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eFixedTransformHintChanged ||
@@ -886,6 +926,10 @@
         updateLayerBounds(snapshot, requested, parentSnapshot, primaryDisplayRotationFlags);
     }
 
+    if (snapshot.edgeExtensionEffect.hasEffect()) {
+        updateBoundsForEdgeExtension(snapshot);
+    }
+
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eCornerRadiusChanged ||
         snapshot.changes.any(RequestedLayerState::Changes::Geometry |
                              RequestedLayerState::Changes::BufferUsageFlags)) {
@@ -904,8 +948,8 @@
     }
 
     // computed snapshot properties
-    snapshot.forceClientComposition =
-            snapshot.shadowSettings.length > 0 || snapshot.stretchEffect.hasEffect();
+    snapshot.forceClientComposition = snapshot.shadowSettings.length > 0 ||
+            snapshot.stretchEffect.hasEffect() || snapshot.edgeExtensionEffect.hasEffect();
     snapshot.contentOpaque = snapshot.isContentOpaque();
     snapshot.isOpaque = snapshot.contentOpaque && !snapshot.roundedCorner.hasRoundedCorners() &&
             snapshot.color.a == 1.f;
@@ -960,6 +1004,31 @@
     }
 }
 
+/**
+ * According to the edges that we are requested to extend, we increase the bounds to the maximum
+ * extension allowed by the crop (parent crop + requested crop). The animation that called
+ * Transition#setEdgeExtensionEffect is in charge of setting the requested crop.
+ * @param snapshot
+ */
+void LayerSnapshotBuilder::updateBoundsForEdgeExtension(LayerSnapshot& snapshot) {
+    EdgeExtensionEffect& effect = snapshot.edgeExtensionEffect;
+
+    if (effect.extendsEdge(LEFT)) {
+        snapshot.geomLayerBounds.left = snapshot.geomLayerCrop.left;
+    }
+    if (effect.extendsEdge(RIGHT)) {
+        snapshot.geomLayerBounds.right = snapshot.geomLayerCrop.right;
+    }
+    if (effect.extendsEdge(TOP)) {
+        snapshot.geomLayerBounds.top = snapshot.geomLayerCrop.top;
+    }
+    if (effect.extendsEdge(BOTTOM)) {
+        snapshot.geomLayerBounds.bottom = snapshot.geomLayerCrop.bottom;
+    }
+
+    snapshot.transformedBounds = snapshot.geomLayerTransform.transform(snapshot.geomLayerBounds);
+}
+
 void LayerSnapshotBuilder::updateLayerBounds(LayerSnapshot& snapshot,
                                              const RequestedLayerState& requested,
                                              const LayerSnapshot& parentSnapshot,
@@ -999,11 +1068,12 @@
     FloatRect parentBounds = parentSnapshot.geomLayerBounds;
     parentBounds = snapshot.localTransform.inverse().transform(parentBounds);
     snapshot.geomLayerBounds =
-            (requested.externalTexture) ? snapshot.bufferSize.toFloatRect() : parentBounds;
+            requested.externalTexture ? snapshot.bufferSize.toFloatRect() : parentBounds;
+    snapshot.geomLayerCrop = parentBounds;
     if (!requested.crop.isEmpty()) {
-        snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(requested.crop.toFloatRect());
+        snapshot.geomLayerCrop = snapshot.geomLayerCrop.intersect(requested.crop.toFloatRect());
     }
-    snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(parentBounds);
+    snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(snapshot.geomLayerCrop);
     snapshot.transformedBounds = snapshot.geomLayerTransform.transform(snapshot.geomLayerBounds);
     const Rect geomLayerBoundsWithoutTransparentRegion =
             RequestedLayerState::reduce(Rect(snapshot.geomLayerBounds),
@@ -1178,6 +1248,15 @@
     }
 }
 
+void LayerSnapshotBuilder::forEachSnapshot(const Visitor& visitor,
+                                           const ConstPredicate& predicate) {
+    for (int i = 0; i < mNumInterestingSnapshots; i++) {
+        std::unique_ptr<LayerSnapshot>& snapshot = mSnapshots.at((size_t)i);
+        if (!predicate(*snapshot)) continue;
+        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 1cec018..f3c56a4 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -86,6 +86,11 @@
     // Visit each visible snapshot in z-order and move the snapshot if needed
     void forEachVisibleSnapshot(const Visitor& visitor);
 
+    typedef std::function<bool(const LayerSnapshot& snapshot)> ConstPredicate;
+    // Visit each snapshot that satisfies the predicate and move the snapshot if needed with visible
+    // snapshots in z-order
+    void forEachSnapshot(const Visitor& visitor, const ConstPredicate& predicate);
+
     // Visit each snapshot interesting to input reverse z-order
     void forEachInputSnapshot(const ConstVisitor& visitor) const;
 
@@ -108,6 +113,10 @@
     static void resetRelativeState(LayerSnapshot& snapshot);
     static void updateRoundedCorner(LayerSnapshot& snapshot, const RequestedLayerState& layerState,
                                     const LayerSnapshot& parentSnapshot, const Args& args);
+    static bool extensionEdgeSharedWithParent(LayerSnapshot& snapshot,
+                                              const RequestedLayerState& requested,
+                                              const LayerSnapshot& parentSnapshot);
+    static void updateBoundsForEdgeExtension(LayerSnapshot& snapshot);
     void updateLayerBounds(LayerSnapshot& snapshot, const RequestedLayerState& layerState,
                            const LayerSnapshot& parentSnapshot, uint32_t displayRotationFlags);
     static void updateShadows(LayerSnapshot& snapshot, const RequestedLayerState& requested,
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 3e8d740..17d2610 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -19,7 +19,7 @@
 #undef LOG_TAG
 #define LOG_TAG "SurfaceFlinger"
 
-#include <gui/TraceUtils.h>
+#include <common/trace.h>
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
 #include <sys/types.h>
@@ -328,6 +328,7 @@
                 changes |= RequestedLayerState::Changes::GameMode;
             }
         }
+        changes |= RequestedLayerState::Changes::Metadata;
     }
     if (clientState.what & layer_state_t::eFrameRateChanged) {
         const auto compatibility =
@@ -580,8 +581,8 @@
 bool RequestedLayerState::isSimpleBufferUpdate(const layer_state_t& s) const {
     static constexpr uint64_t requiredFlags = layer_state_t::eBufferChanged;
     if ((s.what & requiredFlags) != requiredFlags) {
-        ATRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
-                              (s.what | requiredFlags) & ~s.what);
+        SFTRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
+                               (s.what | requiredFlags) & ~s.what);
         return false;
     }
 
@@ -593,8 +594,8 @@
                      ? 0
                      : (layer_state_t::eAutoRefreshChanged | layer_state_t::eFlagsChanged));
     if (s.what & deniedFlags) {
-        ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
-                              s.what & deniedFlags);
+        SFTRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
+                               s.what & deniedFlags);
         return false;
     }
 
@@ -608,15 +609,16 @@
             layer_state_t::eSidebandStreamChanged | layer_state_t::eColorSpaceAgnosticChanged |
             layer_state_t::eShadowRadiusChanged | layer_state_t::eFixedTransformHintChanged |
             layer_state_t::eTrustedOverlayChanged | layer_state_t::eStretchChanged |
-            layer_state_t::eBufferCropChanged | layer_state_t::eDestinationFrameChanged |
-            layer_state_t::eDimmingEnabledChanged | layer_state_t::eExtendedRangeBrightnessChanged |
+            layer_state_t::eEdgeExtensionChanged | layer_state_t::eBufferCropChanged |
+            layer_state_t::eDestinationFrameChanged | layer_state_t::eDimmingEnabledChanged |
+            layer_state_t::eExtendedRangeBrightnessChanged |
             layer_state_t::eDesiredHdrHeadroomChanged |
             (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed()
                      ? layer_state_t::eFlagsChanged
                      : 0);
     if (changedFlags & deniedChanges) {
-        ATRACE_FORMAT_INSTANT("%s: false [has denied changes flags 0x%" PRIx64 "]", __func__,
-                              changedFlags & deniedChanges);
+        SFTRACE_FORMAT_INSTANT("%s: false [has denied changes flags 0x%" PRIx64 "]", __func__,
+                               changedFlags & deniedChanges);
         return false;
     }
 
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
index d3d9509..a1e8213 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -19,9 +19,9 @@
 #define LOG_TAG "SurfaceFlinger"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <common/trace.h>
 #include <cutils/trace.h>
 #include <utils/Log.h>
-#include <utils/Trace.h>
 #include "FrontEnd/LayerLog.h"
 
 #include "TransactionHandler.h"
@@ -31,7 +31,7 @@
 void TransactionHandler::queueTransaction(TransactionState&& state) {
     mLocklessTransactionQueue.push(std::move(state));
     mPendingTransactionCount.fetch_add(1);
-    ATRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
+    SFTRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
 }
 
 void TransactionHandler::collectTransactions() {
@@ -71,7 +71,7 @@
     applyUnsignaledBufferTransaction(transactions, flushState);
 
     mPendingTransactionCount.fetch_sub(transactions.size());
-    ATRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
+    SFTRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
     return transactions;
 }
 
@@ -83,7 +83,7 @@
 
     // only apply an unsignaled buffer transaction if it's the first one
     if (!transactions.empty()) {
-        ATRACE_NAME("fence unsignaled");
+        SFTRACE_NAME("fence unsignaled");
         return;
     }
 
diff --git a/services/surfaceflinger/HdrLayerInfoReporter.cpp b/services/surfaceflinger/HdrLayerInfoReporter.cpp
index 2788332..85921bb 100644
--- a/services/surfaceflinger/HdrLayerInfoReporter.cpp
+++ b/services/surfaceflinger/HdrLayerInfoReporter.cpp
@@ -19,8 +19,8 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <inttypes.h>
-#include <utils/Trace.h>
 
 #include "HdrLayerInfoReporter.h"
 
@@ -29,7 +29,7 @@
 using base::StringAppendF;
 
 void HdrLayerInfoReporter::dispatchHdrLayerInfo(const HdrLayerInfo& info) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mHdrInfoHistory.size() == 0 || mHdrInfoHistory.back().info != info) {
         mHdrInfoHistory.next() = EventHistoryEntry{info};
     }
@@ -47,7 +47,7 @@
     }
 
     for (const auto& listener : toInvoke) {
-        ATRACE_NAME("invoking onHdrLayerInfoChanged");
+        SFTRACE_NAME("invoking onHdrLayerInfoChanged");
         listener->onHdrLayerInfoChanged(info.numberOfHdrLayers, info.maxW, info.maxH, info.flags,
                                         info.maxDesiredHdrSdrRatio);
     }
diff --git a/services/surfaceflinger/Jank/JankTracker.cpp b/services/surfaceflinger/Jank/JankTracker.cpp
new file mode 100644
index 0000000..8e0e084
--- /dev/null
+++ b/services/surfaceflinger/Jank/JankTracker.cpp
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "JankTracker.h"
+
+#include <android/gui/IJankListener.h>
+#include "BackgroundExecutor.h"
+
+namespace android {
+
+namespace {
+
+constexpr size_t kJankDataBatchSize = 50;
+
+} // anonymous namespace
+
+std::atomic<size_t> JankTracker::sListenerCount(0);
+std::atomic<bool> JankTracker::sCollectAllJankDataForTesting(false);
+
+JankTracker::~JankTracker() {}
+
+void JankTracker::addJankListener(int32_t layerId, sp<IBinder> listener) {
+    // Increment right away, so that if an onJankData call comes in before the background thread has
+    // added this listener, it will not drop the data.
+    sListenerCount++;
+
+    BackgroundExecutor::getLowPriorityInstance().sendCallbacks(
+            {[layerId, listener = std::move(listener)]() {
+                JankTracker& tracker = getInstance();
+                const std::lock_guard<std::mutex> _l(tracker.mLock);
+                tracker.addJankListenerLocked(layerId, listener);
+            }});
+}
+
+void JankTracker::flushJankData(int32_t layerId) {
+    BackgroundExecutor::getLowPriorityInstance().sendCallbacks(
+            {[layerId]() { getInstance().doFlushJankData(layerId); }});
+}
+
+void JankTracker::removeJankListener(int32_t layerId, sp<IBinder> listener, int64_t afterVsync) {
+    BackgroundExecutor::getLowPriorityInstance().sendCallbacks(
+            {[layerId, listener = std::move(listener), afterVsync]() {
+                JankTracker& tracker = getInstance();
+                const std::lock_guard<std::mutex> _l(tracker.mLock);
+                tracker.markJankListenerForRemovalLocked(layerId, listener, afterVsync);
+            }});
+}
+
+void JankTracker::onJankData(int32_t layerId, gui::JankData data) {
+    if (sListenerCount == 0) {
+        return;
+    }
+
+    BackgroundExecutor::getLowPriorityInstance().sendCallbacks(
+            {[layerId, data = std::move(data)]() {
+                JankTracker& tracker = getInstance();
+
+                tracker.mLock.lock();
+                bool hasListeners = tracker.mJankListeners.count(layerId) > 0;
+                tracker.mLock.unlock();
+
+                if (!hasListeners && !sCollectAllJankDataForTesting) {
+                    return;
+                }
+
+                tracker.mJankDataLock.lock();
+                tracker.mJankData.emplace(layerId, data);
+                size_t count = tracker.mJankData.count(layerId);
+                tracker.mJankDataLock.unlock();
+
+                if (count >= kJankDataBatchSize && !sCollectAllJankDataForTesting) {
+                    tracker.doFlushJankData(layerId);
+                }
+            }});
+}
+
+void JankTracker::addJankListenerLocked(int32_t layerId, sp<IBinder> listener) {
+    for (auto it = mJankListeners.find(layerId); it != mJankListeners.end(); it++) {
+        if (it->second.mListener == listener) {
+            // Undo the duplicate increment in addJankListener.
+            sListenerCount--;
+            return;
+        }
+    }
+
+    mJankListeners.emplace(layerId, std::move(listener));
+}
+
+void JankTracker::doFlushJankData(int32_t layerId) {
+    std::vector<gui::JankData> jankData;
+    int64_t maxVsync = transferAvailableJankData(layerId, jankData);
+
+    std::vector<sp<IBinder>> toSend;
+
+    mLock.lock();
+    for (auto it = mJankListeners.find(layerId); it != mJankListeners.end();) {
+        if (!jankData.empty()) {
+            toSend.emplace_back(it->second.mListener);
+        }
+
+        int64_t removeAfter = it->second.mRemoveAfter;
+        if (removeAfter != -1 && removeAfter <= maxVsync) {
+            it = mJankListeners.erase(it);
+            sListenerCount--;
+        } else {
+            it++;
+        }
+    }
+    mLock.unlock();
+
+    for (const auto& listener : toSend) {
+        binder::Status status = interface_cast<gui::IJankListener>(listener)->onJankData(jankData);
+        if (status.exceptionCode() == binder::Status::EX_NULL_POINTER) {
+            // Remove any listeners, where the App side has gone away, without
+            // deregistering.
+            dropJankListener(layerId, listener);
+        }
+    }
+}
+
+void JankTracker::markJankListenerForRemovalLocked(int32_t layerId, sp<IBinder> listener,
+                                                   int64_t afterVysnc) {
+    for (auto it = mJankListeners.find(layerId); it != mJankListeners.end(); it++) {
+        if (it->second.mListener == listener) {
+            it->second.mRemoveAfter = std::max(static_cast<int64_t>(0), afterVysnc);
+            return;
+        }
+    }
+}
+
+int64_t JankTracker::transferAvailableJankData(int32_t layerId,
+                                               std::vector<gui::JankData>& outJankData) {
+    const std::lock_guard<std::mutex> _l(mJankDataLock);
+    int64_t maxVsync = 0;
+    auto range = mJankData.equal_range(layerId);
+    for (auto it = range.first; it != range.second;) {
+        maxVsync = std::max(it->second.frameVsyncId, maxVsync);
+        outJankData.emplace_back(std::move(it->second));
+        it = mJankData.erase(it);
+    }
+    return maxVsync;
+}
+
+void JankTracker::dropJankListener(int32_t layerId, sp<IBinder> listener) {
+    const std::lock_guard<std::mutex> _l(mLock);
+    for (auto it = mJankListeners.find(layerId); it != mJankListeners.end(); it++) {
+        if (it->second.mListener == listener) {
+            mJankListeners.erase(it);
+            sListenerCount--;
+            return;
+        }
+    }
+}
+
+void JankTracker::clearAndStartCollectingAllJankDataForTesting() {
+    BackgroundExecutor::getLowPriorityInstance().flushQueue();
+
+    // Clear all past tracked jank data.
+    JankTracker& tracker = getInstance();
+    const std::lock_guard<std::mutex> _l(tracker.mJankDataLock);
+    tracker.mJankData.clear();
+
+    // Pretend there's at least one listener.
+    sListenerCount++;
+    sCollectAllJankDataForTesting = true;
+}
+
+std::vector<gui::JankData> JankTracker::getCollectedJankDataForTesting(int32_t layerId) {
+    JankTracker& tracker = getInstance();
+    const std::lock_guard<std::mutex> _l(tracker.mJankDataLock);
+
+    auto range = tracker.mJankData.equal_range(layerId);
+    std::vector<gui::JankData> result;
+    std::transform(range.first, range.second, std::back_inserter(result),
+                   [](std::pair<int32_t, gui::JankData> layerIdToJankData) {
+                       return layerIdToJankData.second;
+                   });
+
+    return result;
+}
+
+void JankTracker::clearAndStopCollectingAllJankDataForTesting() {
+    // Undo startCollectingAllJankDataForTesting.
+    sListenerCount--;
+    sCollectAllJankDataForTesting = false;
+
+    // Clear all tracked jank data.
+    JankTracker& tracker = getInstance();
+    const std::lock_guard<std::mutex> _l(tracker.mJankDataLock);
+    tracker.mJankData.clear();
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/Jank/JankTracker.h b/services/surfaceflinger/Jank/JankTracker.h
new file mode 100644
index 0000000..5917358
--- /dev/null
+++ b/services/surfaceflinger/Jank/JankTracker.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <mutex>
+#include <unordered_map>
+
+#include <android/gui/JankData.h>
+#include <binder/IBinder.h>
+#include <utils/Mutex.h>
+
+namespace android {
+namespace frametimeline {
+class FrameTimelineTest;
+}
+
+/**
+ * JankTracker maintains a backlog of frame jank classification and manages and notififies any
+ * registered jank data listeners.
+ */
+class JankTracker {
+public:
+    ~JankTracker();
+
+    static void addJankListener(int32_t layerId, sp<IBinder> listener);
+    static void flushJankData(int32_t layerId);
+    static void removeJankListener(int32_t layerId, sp<IBinder> listener, int64_t afterVysnc);
+
+    static void onJankData(int32_t layerId, gui::JankData data);
+
+protected:
+    // The following methods can be used to force the tracker to collect all jank data and not
+    // flush it for a short time period and should *only* be used for testing. Every call to
+    // clearAndStartCollectingAllJankDataForTesting needs to be followed by a call to
+    // clearAndStopCollectingAllJankDataForTesting.
+    static void clearAndStartCollectingAllJankDataForTesting();
+    static std::vector<gui::JankData> getCollectedJankDataForTesting(int32_t layerId);
+    static void clearAndStopCollectingAllJankDataForTesting();
+
+    friend class frametimeline::FrameTimelineTest;
+
+private:
+    JankTracker() {}
+    JankTracker(const JankTracker&) = delete;
+    JankTracker(JankTracker&&) = delete;
+
+    JankTracker& operator=(const JankTracker&) = delete;
+    JankTracker& operator=(JankTracker&&) = delete;
+
+    static JankTracker& getInstance() {
+        static JankTracker instance;
+        return instance;
+    }
+
+    void addJankListenerLocked(int32_t layerId, sp<IBinder> listener) REQUIRES(mLock);
+    void doFlushJankData(int32_t layerId);
+    void markJankListenerForRemovalLocked(int32_t layerId, sp<IBinder> listener, int64_t afterVysnc)
+            REQUIRES(mLock);
+
+    int64_t transferAvailableJankData(int32_t layerId, std::vector<gui::JankData>& jankData);
+    void dropJankListener(int32_t layerId, sp<IBinder> listener);
+
+    struct Listener {
+        sp<IBinder> mListener;
+        int64_t mRemoveAfter;
+
+        Listener(sp<IBinder>&& listener) : mListener(listener), mRemoveAfter(-1) {}
+    };
+
+    // We keep track of the current listener count, so that the onJankData call, which is on the
+    // main thread, can short-curcuit the scheduling on the background thread (which involves
+    // locking) if there are no listeners registered, which is the most common case.
+    static std::atomic<size_t> sListenerCount;
+    static std::atomic<bool> sCollectAllJankDataForTesting;
+
+    std::mutex mLock;
+    std::unordered_multimap<int32_t, Listener> mJankListeners GUARDED_BY(mLock);
+    std::mutex mJankDataLock;
+    std::unordered_multimap<int32_t, gui::JankData> mJankData GUARDED_BY(mJankDataLock);
+
+    friend class JankTrackerTest;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index d27bfd2..86c7d16 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -27,6 +27,7 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <binder/IPCThreadState.h>
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/LayerFECompositionState.h>
@@ -39,7 +40,6 @@
 #include <ftl/fake_guard.h>
 #include <gui/BufferItem.h>
 #include <gui/Surface.h>
-#include <gui/TraceUtils.h>
 #include <math.h>
 #include <private/android_filesystem_config.h>
 #include <renderengine/RenderEngine.h>
@@ -58,7 +58,6 @@
 #include <utils/Log.h>
 #include <utils/NativeHandle.h>
 #include <utils/StopWatch.h>
-#include <utils/Trace.h>
 
 #include <algorithm>
 #include <mutex>
@@ -767,47 +766,12 @@
     return (p != nullptr) ? p->isSecure() : false;
 }
 
-void Layer::transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
-                                      std::vector<JankData>& jankData) {
-    if (mPendingJankClassifications.empty() ||
-        !mPendingJankClassifications.front()->getJankType()) {
-        return;
-    }
-
-    bool includeJankData = false;
-    for (const auto& handle : handles) {
-        for (const auto& cb : handle->callbackIds) {
-            if (cb.includeJankData) {
-                includeJankData = true;
-                break;
-            }
-        }
-
-        if (includeJankData) {
-            jankData.reserve(mPendingJankClassifications.size());
-            break;
-        }
-    }
-
-    while (!mPendingJankClassifications.empty() &&
-           mPendingJankClassifications.front()->getJankType()) {
-        if (includeJankData) {
-            std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame =
-                    mPendingJankClassifications.front();
-            jankData.emplace_back(JankData(surfaceFrame->getToken(),
-                                           surfaceFrame->getJankType().value(),
-                                           surfaceFrame->getRenderRate().getPeriodNsecs()));
-        }
-        mPendingJankClassifications.pop_front();
-    }
-}
-
 // ----------------------------------------------------------------------------
 // transaction
 // ----------------------------------------------------------------------------
 
 uint32_t Layer::doTransaction(uint32_t flags) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     // TODO: This is unfortunate.
     mDrawingStateModified = mDrawingState.modified;
@@ -1436,7 +1400,6 @@
     if (fps) {
         surfaceFrame->setRenderRate(*fps);
     }
-    onSurfaceFrameCreated(surfaceFrame);
     return surfaceFrame;
 }
 
@@ -1453,7 +1416,6 @@
     if (fps) {
         surfaceFrame->setRenderRate(*fps);
     }
-    onSurfaceFrameCreated(surfaceFrame);
     return surfaceFrame;
 }
 
@@ -1479,7 +1441,6 @@
     if (fps) {
         surfaceFrame->setRenderRate(*fps);
     }
-    onSurfaceFrameCreated(surfaceFrame);
     addSurfaceFrameDroppedForBuffer(surfaceFrame, postTime);
 }
 
@@ -2821,7 +2782,7 @@
     if (!listener) {
         return;
     }
-    ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber);
+    SFTRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber);
     uint32_t currentMaxAcquiredBufferCount =
             mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid);
     listener->onReleaseBuffer({buffer->getId(), framenumber},
@@ -2942,33 +2903,14 @@
     }
 }
 
-void Layer::onSurfaceFrameCreated(
-        const std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame) {
-    while (mPendingJankClassifications.size() >= kPendingClassificationMaxSurfaceFrames) {
-        // Too many SurfaceFrames pending classification. The front of the deque is probably not
-        // tracked by FrameTimeline and will never be presented. This will only result in a memory
-        // leak.
-        if (hasBufferOrSidebandStreamInDrawing()) {
-            // Only log for layers with a buffer, since we expect the jank data to be drained for
-            // these, while there may be no jank listeners for bufferless layers.
-            ALOGW("Removing the front of pending jank deque from layer - %s to prevent memory leak",
-                  mName.c_str());
-            std::string miniDump = mPendingJankClassifications.front()->miniDump();
-            ALOGD("Head SurfaceFrame mini dump\n%s", miniDump.c_str());
-        }
-        mPendingJankClassifications.pop_front();
-    }
-    mPendingJankClassifications.emplace_back(surfaceFrame);
-}
-
 void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) {
     for (const auto& handle : mDrawingState.callbackHandles) {
         handle->transformHint = mTransformHint;
         handle->dequeueReadyTime = dequeueReadyTime;
         handle->currentMaxAcquiredBufferCount =
                 mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid);
-        ATRACE_FORMAT_INSTANT("releasePendingBuffer %s - %" PRIu64, getDebugName(),
-                              handle->previousReleaseCallbackId.framenumber);
+        SFTRACE_FORMAT_INSTANT("releasePendingBuffer %s - %" PRIu64, getDebugName(),
+                               handle->previousReleaseCallbackId.framenumber);
     }
 
     for (auto& handle : mDrawingState.callbackHandles) {
@@ -2978,10 +2920,7 @@
         }
     }
 
-    std::vector<JankData> jankData;
-    transferAvailableJankData(mDrawingState.callbackHandles, jankData);
-    mFlinger->getTransactionCallbackInvoker().addCallbackHandles(mDrawingState.callbackHandles,
-                                                                 jankData);
+    mFlinger->getTransactionCallbackInvoker().addCallbackHandles(mDrawingState.callbackHandles);
     mDrawingState.callbackHandles = {};
 }
 
@@ -3150,13 +3089,13 @@
 bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer,
                       const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime,
                       bool isAutoTimestamp, const FrameTimelineInfo& info) {
-    ATRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false"));
+    SFTRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false"));
 
     const bool frameNumberChanged =
             bufferData.flags.test(BufferData::BufferDataChange::frameNumberChanged);
     const uint64_t frameNumber =
             frameNumberChanged ? bufferData.frameNumber : mDrawingState.frameNumber + 1;
-    ATRACE_FORMAT_INSTANT("setBuffer %s - %" PRIu64, getDebugName(), frameNumber);
+    SFTRACE_FORMAT_INSTANT("setBuffer %s - %" PRIu64, getDebugName(), frameNumber);
 
     if (mDrawingState.buffer) {
         releasePreviousBuffer();
@@ -3251,10 +3190,10 @@
 }
 
 void Layer::recordLayerHistoryBufferUpdate(const scheduler::LayerProps& layerProps, nsecs_t now) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     const nsecs_t presentTime = [&] {
         if (!mDrawingState.isAutoTimestamp) {
-            ATRACE_FORMAT_INSTANT("desiredPresentTime");
+            SFTRACE_FORMAT_INSTANT("desiredPresentTime");
             return mDrawingState.desiredPresentTime;
         }
 
@@ -3263,7 +3202,7 @@
                     mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(
                             mDrawingState.latchedVsyncId);
             if (prediction.has_value()) {
-                ATRACE_FORMAT_INSTANT("predictedPresentTime");
+                SFTRACE_FORMAT_INSTANT("predictedPresentTime");
                 mMaxTimeForUseVsyncId = prediction->presentTime +
                         scheduler::LayerHistory::kMaxPeriodForHistory.count();
                 return prediction->presentTime;
@@ -3301,7 +3240,7 @@
 
     if (ATRACE_ENABLED() && presentTime > 0) {
         const auto presentIn = TimePoint::fromNs(presentTime) - TimePoint::now();
-        ATRACE_FORMAT_INSTANT("presentIn %s", to_string(presentIn).c_str());
+        SFTRACE_FORMAT_INSTANT("presentIn %s", to_string(presentIn).c_str());
     }
 
     mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime, now,
@@ -3449,9 +3388,7 @@
     if (!remainingHandles.empty()) {
         // Notify the transaction completed threads these handles are done. These are only the
         // handles that were not added to the mDrawingState, which will be notified later.
-        std::vector<JankData> jankData;
-        transferAvailableJankData(remainingHandles, jankData);
-        mFlinger->getTransactionCallbackInvoker().addCallbackHandles(remainingHandles, jankData);
+        mFlinger->getTransactionCallbackInvoker().addCallbackHandles(remainingHandles);
     }
 
     mReleasePreviousBuffer = false;
@@ -3627,12 +3564,12 @@
         ui::Dataspace dataspace = ui::Dataspace::UNKNOWN;
         status_t err = OK;
         {
-            ATRACE_NAME("getDataspace");
+            SFTRACE_NAME("getDataspace");
             err = mapper.getDataspace(mBufferInfo.mBuffer->getBuffer()->handle, &dataspace);
         }
         if (err != OK || dataspace != mBufferInfo.mDataspace) {
             {
-                ATRACE_NAME("setDataspace");
+                SFTRACE_NAME("setDataspace");
                 err = mapper.setDataspace(mBufferInfo.mBuffer->getBuffer()->handle,
                                           static_cast<ui::Dataspace>(mBufferInfo.mDataspace));
             }
@@ -3696,7 +3633,7 @@
 }
 
 void Layer::tracePendingBufferCount(int32_t pendingBuffers) {
-    ATRACE_INT(mBlastTransactionName.c_str(), pendingBuffers);
+    SFTRACE_INT(mBlastTransactionName.c_str(), pendingBuffers);
 }
 
 /*
@@ -3774,41 +3711,41 @@
                      : layer_state_t::eAutoRefreshChanged);
 
     if ((s.what & requiredFlags) != requiredFlags) {
-        ATRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
-                              (s.what | requiredFlags) & ~s.what);
+        SFTRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
+                               (s.what | requiredFlags) & ~s.what);
         return false;
     }
 
     if (s.what & deniedFlags) {
-        ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
-                              s.what & deniedFlags);
+        SFTRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
+                               s.what & deniedFlags);
         return false;
     }
 
     if (s.what & layer_state_t::ePositionChanged) {
         if (mRequestedTransform.tx() != s.x || mRequestedTransform.ty() != s.y) {
-            ATRACE_FORMAT_INSTANT("%s: false [ePositionChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [ePositionChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eAlphaChanged) {
         if (mDrawingState.color.a != s.color.a) {
-            ATRACE_FORMAT_INSTANT("%s: false [eAlphaChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eAlphaChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eColorTransformChanged) {
         if (mDrawingState.colorTransform != s.colorTransform) {
-            ATRACE_FORMAT_INSTANT("%s: false [eColorTransformChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eColorTransformChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eBackgroundColorChanged) {
         if (mDrawingState.bgColorLayer || s.bgColor.a != 0) {
-            ATRACE_FORMAT_INSTANT("%s: false [eBackgroundColorChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eBackgroundColorChanged changed]", __func__);
             return false;
         }
     }
@@ -3818,92 +3755,92 @@
             mRequestedTransform.dtdy() != s.matrix.dtdy ||
             mRequestedTransform.dtdx() != s.matrix.dtdx ||
             mRequestedTransform.dsdy() != s.matrix.dsdy) {
-            ATRACE_FORMAT_INSTANT("%s: false [eMatrixChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eMatrixChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eCornerRadiusChanged) {
         if (mDrawingState.cornerRadius != s.cornerRadius) {
-            ATRACE_FORMAT_INSTANT("%s: false [eCornerRadiusChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eCornerRadiusChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eBackgroundBlurRadiusChanged) {
         if (mDrawingState.backgroundBlurRadius != static_cast<int>(s.backgroundBlurRadius)) {
-            ATRACE_FORMAT_INSTANT("%s: false [eBackgroundBlurRadiusChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eBackgroundBlurRadiusChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eBufferTransformChanged) {
         if (mDrawingState.bufferTransform != s.bufferTransform) {
-            ATRACE_FORMAT_INSTANT("%s: false [eBufferTransformChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eBufferTransformChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eTransformToDisplayInverseChanged) {
         if (mDrawingState.transformToDisplayInverse != s.transformToDisplayInverse) {
-            ATRACE_FORMAT_INSTANT("%s: false [eTransformToDisplayInverseChanged changed]",
-                                  __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eTransformToDisplayInverseChanged changed]",
+                                   __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eCropChanged) {
         if (mDrawingState.crop != s.crop) {
-            ATRACE_FORMAT_INSTANT("%s: false [eCropChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eCropChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eDataspaceChanged) {
         if (mDrawingState.dataspace != s.dataspace) {
-            ATRACE_FORMAT_INSTANT("%s: false [eDataspaceChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eDataspaceChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eHdrMetadataChanged) {
         if (mDrawingState.hdrMetadata != s.hdrMetadata) {
-            ATRACE_FORMAT_INSTANT("%s: false [eHdrMetadataChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eHdrMetadataChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eSidebandStreamChanged) {
         if (mDrawingState.sidebandStream != s.sidebandStream) {
-            ATRACE_FORMAT_INSTANT("%s: false [eSidebandStreamChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eSidebandStreamChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eColorSpaceAgnosticChanged) {
         if (mDrawingState.colorSpaceAgnostic != s.colorSpaceAgnostic) {
-            ATRACE_FORMAT_INSTANT("%s: false [eColorSpaceAgnosticChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eColorSpaceAgnosticChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eShadowRadiusChanged) {
         if (mDrawingState.shadowRadius != s.shadowRadius) {
-            ATRACE_FORMAT_INSTANT("%s: false [eShadowRadiusChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eShadowRadiusChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eFixedTransformHintChanged) {
         if (mDrawingState.fixedTransformHint != s.fixedTransformHint) {
-            ATRACE_FORMAT_INSTANT("%s: false [eFixedTransformHintChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eFixedTransformHintChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eTrustedOverlayChanged) {
         if (mDrawingState.isTrustedOverlay != (s.trustedOverlay == gui::TrustedOverlay::ENABLED)) {
-            ATRACE_FORMAT_INSTANT("%s: false [eTrustedOverlayChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eTrustedOverlayChanged changed]", __func__);
             return false;
         }
     }
@@ -3912,28 +3849,28 @@
         StretchEffect temp = s.stretchEffect;
         temp.sanitize();
         if (mDrawingState.stretchEffect != temp) {
-            ATRACE_FORMAT_INSTANT("%s: false [eStretchChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eStretchChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eBufferCropChanged) {
         if (mDrawingState.bufferCrop != s.bufferCrop) {
-            ATRACE_FORMAT_INSTANT("%s: false [eBufferCropChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eBufferCropChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eDestinationFrameChanged) {
         if (mDrawingState.destinationFrame != s.destinationFrame) {
-            ATRACE_FORMAT_INSTANT("%s: false [eDestinationFrameChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eDestinationFrameChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eDimmingEnabledChanged) {
         if (mDrawingState.dimmingEnabled != s.dimmingEnabled) {
-            ATRACE_FORMAT_INSTANT("%s: false [eDimmingEnabledChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eDimmingEnabledChanged changed]", __func__);
             return false;
         }
     }
@@ -3941,14 +3878,14 @@
     if (s.what & layer_state_t::eExtendedRangeBrightnessChanged) {
         if (mDrawingState.currentHdrSdrRatio != s.currentHdrSdrRatio ||
             mDrawingState.desiredHdrSdrRatio != s.desiredHdrSdrRatio) {
-            ATRACE_FORMAT_INSTANT("%s: false [eExtendedRangeBrightnessChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eExtendedRangeBrightnessChanged changed]", __func__);
             return false;
         }
     }
 
     if (s.what & layer_state_t::eDesiredHdrHeadroomChanged) {
         if (mDrawingState.desiredHdrSdrRatio != s.desiredHdrSdrRatio) {
-            ATRACE_FORMAT_INSTANT("%s: false [eDesiredHdrHeadroomChanged changed]", __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false [eDesiredHdrHeadroomChanged changed]", __func__);
             return false;
         }
     }
@@ -4140,8 +4077,8 @@
 }
 
 bool Layer::latchBufferImpl(bool& recomputeVisibleRegions, nsecs_t latchTime, bool bgColorOnly) {
-    ATRACE_FORMAT_INSTANT("latchBuffer %s - %" PRIu64, getDebugName(),
-                          getDrawingState().frameNumber);
+    SFTRACE_FORMAT_INSTANT("latchBuffer %s - %" PRIu64, getDebugName(),
+                           getDrawingState().frameNumber);
 
     bool refreshRequired = latchSidebandStream(recomputeVisibleRegions);
 
@@ -4152,7 +4089,7 @@
     // If the head buffer's acquire fence hasn't signaled yet, return and
     // try again later
     if (!fenceHasSignaled()) {
-        ATRACE_NAME("!fenceHasSignaled()");
+        SFTRACE_NAME("!fenceHasSignaled()");
         mFlinger->onLayerUpdate();
         return false;
     }
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index b9fcd5c..d3b56f8 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -980,7 +980,6 @@
     void preparePerFrameBufferCompositionState();
     void preparePerFrameEffectsCompositionState();
     void gatherBufferInfo();
-    void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>&);
 
     bool isClonedFromAlive() { return getClonedFrom() != nullptr; }
 
@@ -1195,11 +1194,6 @@
 
     bool hasSomethingToDraw() const { return hasEffect() || hasBufferOrSidebandStream(); }
 
-    // Fills the provided vector with the currently available JankData and removes the processed
-    // JankData from the pending list.
-    void transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
-                                   std::vector<JankData>& jankData);
-
     bool shouldOverrideChildrenFrameRate() const {
         return getDrawingState().frameRateSelectionStrategy ==
                 FrameRateSelectionStrategy::OverrideChildren;
@@ -1268,10 +1262,6 @@
     // time.
     std::variant<nsecs_t, sp<Fence>> mCallbackHandleAcquireTimeOrFence = -1;
 
-    std::deque<std::shared_ptr<android::frametimeline::SurfaceFrame>> mPendingJankClassifications;
-    // An upper bound on the number of SurfaceFrames in the pending classifications deque.
-    static constexpr int kPendingClassificationMaxSurfaceFrames = 50;
-
     const std::string mBlastTransactionName{"BufferTX - " + mName};
     // This integer is incremented everytime a buffer arrives at the server for this layer,
     // and decremented when a buffer is dropped or latched. When changed the integer is exported
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index c2251a8..b05f0ee 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -19,11 +19,10 @@
 #define LOG_TAG "SurfaceFlinger"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <common/trace.h>
 #include <gui/GLConsumer.h>
-#include <gui/TraceUtils.h>
 #include <math/vec3.h>
 #include <system/window.h>
-#include <utils/Log.h>
 
 #include "LayerFE.h"
 #include "SurfaceFlinger.h"
@@ -122,7 +121,7 @@
 
 std::optional<compositionengine::LayerFE::LayerSettings> LayerFE::prepareClientCompositionInternal(
         compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     compositionengine::LayerFE::LayerSettings layerSettings;
     layerSettings.geometry.boundaries =
             reduce(mSnapshot->geomLayerBounds, mSnapshot->transparentRegionHint);
@@ -174,6 +173,7 @@
             break;
     }
     layerSettings.stretchEffect = mSnapshot->stretchEffect;
+    layerSettings.edgeExtensionEffect = mSnapshot->edgeExtensionEffect;
     // Record the name of the layer for debugging further down the stack.
     layerSettings.name = mSnapshot->name;
 
@@ -214,7 +214,7 @@
 void LayerFE::prepareBufferStateClientComposition(
         compositionengine::LayerFE::LayerSettings& layerSettings,
         compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (CC_UNLIKELY(!mSnapshot->externalTexture)) {
         // If there is no buffer for the layer or we have sidebandstream where there is no
         // activeBuffer, then we need to return LayerSettings.
diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp
index f323ce7..bfe6d2a 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -27,10 +27,9 @@
 
 LayerRenderArea::LayerRenderArea(sp<Layer> layer, frontend::LayerSnapshot layerSnapshot,
                                  const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
-                                 bool allowSecureLayers, const ui::Transform& layerTransform,
-                                 const Rect& layerBufferSize, bool hintForSeamlessTransition)
-      : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, hintForSeamlessTransition,
-                   allowSecureLayers),
+                                 const ui::Transform& layerTransform, const Rect& layerBufferSize,
+                                 ftl::Flags<RenderArea::Options> options)
+      : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, options),
         mLayer(std::move(layer)),
         mLayerSnapshot(std::move(layerSnapshot)),
         mLayerBufferSize(layerBufferSize),
@@ -42,7 +41,7 @@
 }
 
 bool LayerRenderArea::isSecure() const {
-    return mAllowSecureLayers;
+    return mOptions.test(Options::CAPTURE_SECURE_LAYERS);
 }
 
 sp<const DisplayDevice> LayerRenderArea::getDisplayDevice() const {
diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h
index a12bfca..f72c7c7 100644
--- a/services/surfaceflinger/LayerRenderArea.h
+++ b/services/surfaceflinger/LayerRenderArea.h
@@ -33,9 +33,9 @@
 class LayerRenderArea : public RenderArea {
 public:
     LayerRenderArea(sp<Layer> layer, frontend::LayerSnapshot layerSnapshot, const Rect& crop,
-                    ui::Size reqSize, ui::Dataspace reqDataSpace, bool allowSecureLayers,
+                    ui::Size reqSize, ui::Dataspace reqDataSpace,
                     const ui::Transform& layerTransform, const Rect& layerBufferSize,
-                    bool hintForSeamlessTransition);
+                    ftl::Flags<RenderArea::Options> options);
 
     const ui::Transform& getTransform() const override;
     bool isSecure() const override;
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 5add290..7712d38 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -26,6 +26,7 @@
 
 #include "RegionSamplingThread.h"
 
+#include <common/trace.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <cutils/properties.h>
@@ -34,7 +35,6 @@
 #include <gui/SyncScreenCaptureListener.h>
 #include <renderengine/impl/ExternalTexture.h>
 #include <ui/DisplayStatInfo.h>
-#include <utils/Trace.h>
 
 #include <string>
 
@@ -148,7 +148,7 @@
     std::lock_guard lock(mThreadControlMutex);
 
     if (mSampleRequestTime.has_value()) {
-        ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForSamplePhase));
+        SFTRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForSamplePhase));
         mSampleRequestTime.reset();
         mFlinger.scheduleSample();
     }
@@ -166,7 +166,7 @@
     if (mLastSampleTime + mTunables.mSamplingPeriod > now) {
         // content changed, but we sampled not too long ago, so we need to sample some time in the
         // future.
-        ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::idleTimerWaiting));
+        SFTRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::idleTimerWaiting));
         mSampleRequestTime = now;
         return;
     }
@@ -175,13 +175,13 @@
         // until the next vsync deadline, defer this sampling work
         // to a later frame, when hopefully there will be more time.
         if (samplingDeadline.has_value() && now + mTunables.mSamplingDuration > *samplingDeadline) {
-            ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForQuietFrame));
+            SFTRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForQuietFrame));
             mSampleRequestTime = mSampleRequestTime.value_or(now);
             return;
         }
     }
 
-    ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::sample));
+    SFTRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::sample));
 
     mSampleRequestTime.reset();
     mLastSampleTime = now;
@@ -247,7 +247,7 @@
 }
 
 void RegionSamplingThread::captureSample() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard lock(mSamplingMutex);
 
     if (mDescriptors.empty()) {
@@ -277,7 +277,6 @@
     }
 
     const Rect sampledBounds = sampleRegion.bounds();
-    constexpr bool kHintForSeamlessTransition = false;
 
     std::unordered_set<sp<IRegionSamplingListener>, SpHash<IRegionSamplingListener>> listeners;
 
@@ -350,17 +349,15 @@
 
     SurfaceFlinger::RenderAreaBuilderVariant
             renderAreaBuilder(std::in_place_type<DisplayRenderAreaBuilder>, sampledBounds,
-                              sampledBounds.getSize(), ui::Dataspace::V0_SRGB,
-                              kHintForSeamlessTransition, true /* captureSecureLayers */,
-                              displayWeak);
+                              sampledBounds.getSize(), ui::Dataspace::V0_SRGB, displayWeak,
+                              RenderArea::Options::CAPTURE_SECURE_LAYERS);
 
     FenceResult fenceResult;
     if (FlagManager::getInstance().single_hop_screenshot() &&
-        FlagManager::getInstance().ce_fence_promise()) {
+        FlagManager::getInstance().ce_fence_promise() && mFlinger.mRenderEngine->isThreaded()) {
         std::vector<sp<LayerFE>> layerFEs;
-        auto displayState =
-                mFlinger.getDisplayAndLayerSnapshotsFromMainThread(renderAreaBuilder,
-                                                                   getLayerSnapshotsFn, layerFEs);
+        auto displayState = mFlinger.getSnapshotsFromMainThread(renderAreaBuilder,
+                                                                getLayerSnapshotsFn, layerFEs);
         fenceResult =
                 mFlinger.captureScreenshot(renderAreaBuilder, buffer, kRegionSampling, kGrayscale,
                                            kIsProtected, nullptr, displayState, layerFEs)
@@ -396,7 +393,7 @@
     }
 
     mCachedBuffer = buffer;
-    ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::noWorkNeeded));
+    SFTRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::noWorkNeeded));
 }
 
 // NO_THREAD_SAFETY_ANALYSIS is because std::unique_lock presently lacks thread safety annotations.
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index e8d20af..034e467 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -21,16 +21,23 @@
 class RenderArea {
 public:
     enum class CaptureFill {CLEAR, OPAQUE};
+    enum class Options {
+        // If not set, the secure layer would be blacked out or skipped
+        // when rendered to an insecure render area
+        CAPTURE_SECURE_LAYERS = 1 << 0,
 
+        // If set, the render result may be used for system animations
+        // that must preserve the exact colors of the display
+        HINT_FOR_SEAMLESS_TRANSITION = 1 << 1,
+    };
     static float getCaptureFillValue(CaptureFill captureFill);
 
     RenderArea(ui::Size reqSize, CaptureFill captureFill, ui::Dataspace reqDataSpace,
-               bool hintForSeamlessTransition, bool allowSecureLayers = false)
-          : mAllowSecureLayers(allowSecureLayers),
+               ftl::Flags<Options> options)
+          : mOptions(options),
             mReqSize(reqSize),
             mReqDataSpace(reqDataSpace),
-            mCaptureFill(captureFill),
-            mHintForSeamlessTransition(hintForSeamlessTransition) {}
+            mCaptureFill(captureFill) {}
 
     static std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> fromTraverseLayersLambda(
             std::function<void(const LayerVector::Visitor&)> traverseLayers) {
@@ -90,16 +97,17 @@
 
     // Returns whether the render result may be used for system animations that
     // must preserve the exact colors of the display.
-    bool getHintForSeamlessTransition() const { return mHintForSeamlessTransition; }
+    bool getHintForSeamlessTransition() const {
+        return mOptions.test(Options::HINT_FOR_SEAMLESS_TRANSITION);
+    }
 
 protected:
-    const bool mAllowSecureLayers;
+    ftl::Flags<Options> mOptions;
 
 private:
     const ui::Size mReqSize;
     const ui::Dataspace mReqDataSpace;
     const CaptureFill mCaptureFill;
-    const bool mHintForSeamlessTransition;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/RenderAreaBuilder.h b/services/surfaceflinger/RenderAreaBuilder.h
index a25c6e0..599fa7e 100644
--- a/services/surfaceflinger/RenderAreaBuilder.h
+++ b/services/surfaceflinger/RenderAreaBuilder.h
@@ -36,50 +36,34 @@
     // Composition data space of the render area
     ui::Dataspace reqDataSpace;
 
-    // If true, the secure layer would be blacked out or skipped
-    // when rendered to an insecure render area
-    bool allowSecureLayers;
-
-    // If true, the render result may be used for system animations
-    // that must preserve the exact colors of the display
-    bool hintForSeamlessTransition;
-
+    ftl::Flags<RenderArea::Options> options;
     virtual std::unique_ptr<RenderArea> build() const = 0;
 
     RenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
-                      bool allowSecureLayers, bool hintForSeamlessTransition)
-          : crop(crop),
-            reqSize(reqSize),
-            reqDataSpace(reqDataSpace),
-            allowSecureLayers(allowSecureLayers),
-            hintForSeamlessTransition(hintForSeamlessTransition) {}
+                      ftl::Flags<RenderArea::Options> options)
+          : crop(crop), reqSize(reqSize), reqDataSpace(reqDataSpace), options(options) {}
 
     virtual ~RenderAreaBuilder() = default;
 };
 
 struct DisplayRenderAreaBuilder : RenderAreaBuilder {
     DisplayRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
-                             bool allowSecureLayers, bool hintForSeamlessTransition,
-                             wp<const DisplayDevice> displayWeak)
-          : RenderAreaBuilder(crop, reqSize, reqDataSpace, allowSecureLayers,
-                              hintForSeamlessTransition),
-            displayWeak(displayWeak) {}
+                             wp<const DisplayDevice> displayWeak,
+                             ftl::Flags<RenderArea::Options> options)
+          : RenderAreaBuilder(crop, reqSize, reqDataSpace, options), displayWeak(displayWeak) {}
 
     // Display that render area will be on
     wp<const DisplayDevice> displayWeak;
 
     std::unique_ptr<RenderArea> build() const override {
-        return DisplayRenderArea::create(displayWeak, crop, reqSize, reqDataSpace,
-                                         hintForSeamlessTransition, allowSecureLayers);
+        return DisplayRenderArea::create(displayWeak, crop, reqSize, reqDataSpace, options);
     }
 };
 
 struct LayerRenderAreaBuilder : RenderAreaBuilder {
-    LayerRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
-                           bool allowSecureLayers, bool hintForSeamlessTransition, sp<Layer> layer,
-                           bool childrenOnly)
-          : RenderAreaBuilder(crop, reqSize, reqDataSpace, allowSecureLayers,
-                              hintForSeamlessTransition),
+    LayerRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace, sp<Layer> layer,
+                           bool childrenOnly, ftl::Flags<RenderArea::Options> options)
+          : RenderAreaBuilder(crop, reqSize, reqDataSpace, options),
             layer(layer),
             childrenOnly(childrenOnly) {}
 
@@ -110,8 +94,8 @@
 
     std::unique_ptr<RenderArea> build() const override {
         return std::make_unique<LayerRenderArea>(layer, std::move(layerSnapshot), crop, reqSize,
-                                                 reqDataSpace, allowSecureLayers, layerTransform,
-                                                 layerBufferSize, hintForSeamlessTransition);
+                                                 reqDataSpace, layerTransform, layerBufferSize,
+                                                 options);
     }
 };
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 6b65449..d31fcea 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -33,7 +33,7 @@
 #include <android-base/stringprintf.h>
 
 #include <binder/IPCThreadState.h>
-
+#include <common/trace.h>
 #include <cutils/compiler.h>
 #include <cutils/sched_policy.h>
 
@@ -41,7 +41,6 @@
 #include <gui/SchedulingPolicy.h>
 
 #include <utils/Errors.h>
-#include <utils/Trace.h>
 
 #include <common/FlagManager.h>
 #include <scheduler/VsyncConfig.h>
@@ -226,14 +225,14 @@
 }
 
 binder::Status EventThreadConnection::requestNextVsync() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mEventThread->requestNextVsync(sp<EventThreadConnection>::fromExisting(this));
     return binder::Status::ok();
 }
 
 binder::Status EventThreadConnection::getLatestVsyncEventData(
         ParcelableVsyncEventData* outVsyncEventData) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     outVsyncEventData->vsync =
             mEventThread->getLatestVsyncEventData(sp<EventThreadConnection>::fromExisting(this),
                                                   systemTime());
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index a819b79..4fd4c0e 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -21,8 +21,8 @@
 #include "LayerHistory.h"
 
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <cutils/properties.h>
-#include <gui/TraceUtils.h>
 #include <utils/Log.h>
 #include <utils/Timers.h>
 
@@ -72,7 +72,7 @@
 
 void trace(const LayerInfo& info, LayerHistory::LayerVoteType type, int fps) {
     const auto traceType = [&](LayerHistory::LayerVoteType checkedType, int value) {
-        ATRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
+        SFTRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
     };
 
     traceType(LayerHistory::LayerVoteType::NoVote, 1);
@@ -190,7 +190,7 @@
 }
 
 auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) -> Summary {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     Summary summary;
 
     std::lock_guard lock(mLock);
@@ -204,7 +204,7 @@
         ALOGV("%s has priority: %d %s focused", info->getName().c_str(), frameRateSelectionPriority,
               layerFocused ? "" : "not");
 
-        ATRACE_FORMAT("%s", info->getName().c_str());
+        SFTRACE_FORMAT("%s", info->getName().c_str());
         const auto votes = info->getRefreshRateVote(selector, now);
         for (LayerInfo::LayerVote vote : votes) {
             if (vote.isNoVote()) {
@@ -222,8 +222,8 @@
             const std::string categoryString = vote.category == FrameRateCategory::Default
                     ? ""
                     : base::StringPrintf("category=%s", ftl::enum_string(vote.category).c_str());
-            ATRACE_FORMAT_INSTANT("%s %s %s (%.2f)", ftl::enum_string(vote.type).c_str(),
-                                  to_string(vote.fps).c_str(), categoryString.c_str(), weight);
+            SFTRACE_FORMAT_INSTANT("%s %s %s (%.2f)", ftl::enum_string(vote.type).c_str(),
+                                   to_string(vote.fps).c_str(), categoryString.c_str(), weight);
             summary.push_back({info->getName(), info->getOwnerUid(), vote.type, vote.fps,
                                vote.seamlessness, vote.category, vote.categorySmoothSwitchOnly,
                                weight, layerFocused});
@@ -238,7 +238,7 @@
 }
 
 void LayerHistory::partitionLayers(nsecs_t now, bool isVrrDevice) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     const nsecs_t threshold = getActiveLayerThreshold(now);
 
     // iterate over inactive map
@@ -310,7 +310,7 @@
 
                 if (gameModeFrameRateOverride.isValid()) {
                     info->setLayerVote({gameFrameRateOverrideVoteType, gameModeFrameRateOverride});
-                    ATRACE_FORMAT_INSTANT("GameModeFrameRateOverride");
+                    SFTRACE_FORMAT_INSTANT("GameModeFrameRateOverride");
                     if (CC_UNLIKELY(mTraceEnabled)) {
                         trace(*info, gameFrameRateOverrideVoteType,
                               gameModeFrameRateOverride.getIntValue());
@@ -326,19 +326,19 @@
                 } else if (gameDefaultFrameRateOverride.isValid()) {
                     info->setLayerVote(
                             {gameFrameRateOverrideVoteType, gameDefaultFrameRateOverride});
-                    ATRACE_FORMAT_INSTANT("GameDefaultFrameRateOverride");
+                    SFTRACE_FORMAT_INSTANT("GameDefaultFrameRateOverride");
                     if (CC_UNLIKELY(mTraceEnabled)) {
                         trace(*info, gameFrameRateOverrideVoteType,
                               gameDefaultFrameRateOverride.getIntValue());
                     }
                 } else {
                     if (frameRate.isValid() && !frameRate.isVoteValidForMrr(isVrrDevice)) {
-                        ATRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
-                                              "%s %s",
-                                              info->getName().c_str(),
-                                              ftl::enum_string(frameRate.vote.type).c_str(),
-                                              to_string(frameRate.vote.rate).c_str(),
-                                              ftl::enum_string(frameRate.category).c_str());
+                        SFTRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
+                                               "%s %s",
+                                               info->getName().c_str(),
+                                               ftl::enum_string(frameRate.vote.type).c_str(),
+                                               to_string(frameRate.vote.rate).c_str(),
+                                               ftl::enum_string(frameRate.category).c_str());
                     }
                     info->resetLayerVote();
                 }
@@ -349,12 +349,12 @@
                                         frameRate.vote.seamlessness, frameRate.category});
                 } else {
                     if (!frameRate.isVoteValidForMrr(isVrrDevice)) {
-                        ATRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
-                                              "%s %s",
-                                              info->getName().c_str(),
-                                              ftl::enum_string(frameRate.vote.type).c_str(),
-                                              to_string(frameRate.vote.rate).c_str(),
-                                              ftl::enum_string(frameRate.category).c_str());
+                        SFTRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
+                                               "%s %s",
+                                               info->getName().c_str(),
+                                               ftl::enum_string(frameRate.vote.type).c_str(),
+                                               to_string(frameRate.vote.rate).c_str(),
+                                               ftl::enum_string(frameRate.category).c_str());
                     }
                     info->resetLayerVote();
                 }
@@ -421,7 +421,7 @@
 bool LayerHistory::isSmallDirtyArea(uint32_t dirtyArea, float threshold) const {
     const float ratio = (float)dirtyArea / mDisplayArea;
     const bool isSmallDirty = ratio <= threshold;
-    ATRACE_FORMAT_INSTANT("small dirty=%s, ratio=%.3f", isSmallDirty ? "true" : "false", ratio);
+    SFTRACE_FORMAT_INSTANT("small dirty=%s, ratio=%.3f", isSmallDirty ? "true" : "false", ratio);
     return isSmallDirty;
 }
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 632f42a..a1a60e3 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -27,10 +27,10 @@
 #include <utility>
 
 #include <android/native_window.h>
+#include <common/trace.h>
 #include <cutils/compiler.h>
 #include <cutils/trace.h>
 #include <ftl/enum.h>
-#include <gui/TraceUtils.h>
 #include <system/window.h>
 
 #undef LOG_TAG
@@ -259,7 +259,7 @@
     }
 
     if (smallDirtyCount > 0) {
-        ATRACE_FORMAT_INSTANT("small dirty = %" PRIu32, smallDirtyCount);
+        SFTRACE_FORMAT_INSTANT("small dirty = %" PRIu32, smallDirtyCount);
     }
 
     if (numDeltas == 0) {
@@ -272,7 +272,7 @@
 
 std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(const RefreshRateSelector& selector,
                                                              nsecs_t now) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     static constexpr float MARGIN = 1.0f; // 1Hz
     if (!hasEnoughDataForHeuristic()) {
         ALOGV("Not enough data");
@@ -307,7 +307,7 @@
 
 LayerInfo::RefreshRateVotes LayerInfo::getRefreshRateVote(const RefreshRateSelector& selector,
                                                           nsecs_t now) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     LayerInfo::RefreshRateVotes votes;
 
     if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
@@ -315,8 +315,8 @@
             const auto voteType = mLayerVote.type == LayerHistory::LayerVoteType::NoVote
                     ? LayerHistory::LayerVoteType::NoVote
                     : LayerHistory::LayerVoteType::ExplicitCategory;
-            ATRACE_FORMAT_INSTANT("Vote %s (category=%s)", ftl::enum_string(voteType).c_str(),
-                                  ftl::enum_string(mLayerVote.category).c_str());
+            SFTRACE_FORMAT_INSTANT("Vote %s (category=%s)", ftl::enum_string(voteType).c_str(),
+                                   ftl::enum_string(mLayerVote.category).c_str());
             ALOGV("%s voted %s with category: %s", mName.c_str(),
                   ftl::enum_string(voteType).c_str(),
                   ftl::enum_string(mLayerVote.category).c_str());
@@ -326,7 +326,7 @@
 
         if (mLayerVote.fps.isValid() ||
             mLayerVote.type != LayerHistory::LayerVoteType::ExplicitDefault) {
-            ATRACE_FORMAT_INSTANT("Vote %s", ftl::enum_string(mLayerVote.type).c_str());
+            SFTRACE_FORMAT_INSTANT("Vote %s", ftl::enum_string(mLayerVote.type).c_str());
             ALOGV("%s voted %d", mName.c_str(), static_cast<int>(mLayerVote.type));
             votes.push_back({mLayerVote.type, mLayerVote.fps, mLayerVote.seamlessness,
                              FrameRateCategory::Default, mLayerVote.categorySmoothSwitchOnly});
@@ -336,7 +336,7 @@
     }
 
     if (isAnimating(now)) {
-        ATRACE_FORMAT_INSTANT("animating");
+        SFTRACE_FORMAT_INSTANT("animating");
         ALOGV("%s is animating", mName.c_str());
         mLastRefreshRate.animating = true;
         votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
@@ -345,7 +345,7 @@
 
     // Vote for max refresh rate whenever we're front-buffered.
     if (FlagManager::getInstance().vrr_config() && isFrontBuffered()) {
-        ATRACE_FORMAT_INSTANT("front buffered");
+        SFTRACE_FORMAT_INSTANT("front buffered");
         ALOGV("%s is front-buffered", mName.c_str());
         votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
         return votes;
@@ -354,7 +354,7 @@
     const LayerInfo::Frequent frequent = isFrequent(now);
     mIsFrequencyConclusive = frequent.isConclusive;
     if (!frequent.isFrequent) {
-        ATRACE_FORMAT_INSTANT("infrequent");
+        SFTRACE_FORMAT_INSTANT("infrequent");
         ALOGV("%s is infrequent", mName.c_str());
         mLastRefreshRate.infrequent = true;
         mLastSmallDirtyCount = 0;
@@ -365,14 +365,14 @@
     }
 
     if (frequent.clearHistory) {
-        ATRACE_FORMAT_INSTANT("frequent.clearHistory");
+        SFTRACE_FORMAT_INSTANT("frequent.clearHistory");
         ALOGV("%s frequent.clearHistory", mName.c_str());
         clearHistory(now);
     }
 
     // Return no vote if the recent frames are small dirty.
     if (frequent.isSmallDirty && !mLastRefreshRate.reported.isValid()) {
-        ATRACE_FORMAT_INSTANT("NoVote (small dirty)");
+        SFTRACE_FORMAT_INSTANT("NoVote (small dirty)");
         ALOGV("%s is small dirty", mName.c_str());
         votes.push_back({LayerHistory::LayerVoteType::NoVote, Fps()});
         return votes;
@@ -380,13 +380,13 @@
 
     auto refreshRate = calculateRefreshRateIfPossible(selector, now);
     if (refreshRate.has_value()) {
-        ATRACE_FORMAT_INSTANT("calculated (%s)", to_string(*refreshRate).c_str());
+        SFTRACE_FORMAT_INSTANT("calculated (%s)", to_string(*refreshRate).c_str());
         ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());
         votes.push_back({LayerHistory::LayerVoteType::Heuristic, refreshRate.value()});
         return votes;
     }
 
-    ATRACE_FORMAT_INSTANT("Max (can't resolve refresh rate)");
+    SFTRACE_FORMAT_INSTANT("Max (can't resolve refresh rate)");
     ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
     votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
     return votes;
@@ -452,7 +452,7 @@
             mHeuristicTraceTagData = makeHeuristicTraceTagData();
         }
 
-        ATRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
+        SFTRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
     }
 
     return selectRefreshRate(selector);
@@ -486,9 +486,9 @@
             mHeuristicTraceTagData = makeHeuristicTraceTagData();
         }
 
-        ATRACE_INT(mHeuristicTraceTagData->max.c_str(), max->refreshRate.getIntValue());
-        ATRACE_INT(mHeuristicTraceTagData->min.c_str(), min->refreshRate.getIntValue());
-        ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
+        SFTRACE_INT(mHeuristicTraceTagData->max.c_str(), max->refreshRate.getIntValue());
+        SFTRACE_INT(mHeuristicTraceTagData->min.c_str(), min->refreshRate.getIntValue());
+        SFTRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
     }
 
     return consistent ? maxClosestRate : Fps();
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index ff88d71..6a67ac5 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -57,7 +57,7 @@
         mHandler(std::move(handler)) {}
 
 void MessageQueue::vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     // Trace VSYNC-sf
     mVsync.value = (mVsync.value + 1) % 2;
 
@@ -136,7 +136,7 @@
 }
 
 void MessageQueue::setDuration(std::chrono::nanoseconds workDuration) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard lock(mVsync.mutex);
     mVsync.workDuration = workDuration;
     mVsync.scheduledFrameTimeOpt =
@@ -189,7 +189,7 @@
 }
 
 void MessageQueue::scheduleFrame() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard lock(mVsync.mutex);
     mVsync.scheduledFrameTimeOpt =
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 846727b..0b17c84 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -28,13 +28,12 @@
 
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <ftl/enum.h>
 #include <ftl/fake_guard.h>
 #include <ftl/match.h>
 #include <ftl/unit.h>
-#include <gui/TraceUtils.h>
 #include <scheduler/FrameRateMode.h>
-#include <utils/Trace.h>
 
 #include "RefreshRateSelector.h"
 
@@ -494,7 +493,7 @@
                                                     GlobalSignals signals, Fps pacesetterFps) const
         -> RankedFrameRates {
     using namespace fps_approx_ops;
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("%s: %zu layers", __func__, layers.size());
 
     const auto& activeMode = *getActiveModeLocked().modePtr;
@@ -508,8 +507,8 @@
                                             });
 
         if (!ranking.empty()) {
-            ATRACE_FORMAT_INSTANT("%s (Follower display)",
-                                  to_string(ranking.front().frameRateMode.fps).c_str());
+            SFTRACE_FORMAT_INSTANT("%s (Follower display)",
+                                   to_string(ranking.front().frameRateMode.fps).c_str());
 
             return {ranking, kNoSignals, pacesetterFps};
         }
@@ -521,8 +520,8 @@
     if (signals.powerOnImminent) {
         ALOGV("Power On Imminent");
         const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending);
-        ATRACE_FORMAT_INSTANT("%s (Power On Imminent)",
-                              to_string(ranking.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (Power On Imminent)",
+                               to_string(ranking.front().frameRateMode.fps).c_str());
         return {ranking, GlobalSignals{.powerOnImminent = true}};
     }
 
@@ -608,8 +607,8 @@
     if (signals.touch && !hasExplicitVoteLayers) {
         ALOGV("Touch Boost");
         const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
-        ATRACE_FORMAT_INSTANT("%s (Touch Boost)",
-                              to_string(ranking.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (Touch Boost)",
+                               to_string(ranking.front().frameRateMode.fps).c_str());
         return {ranking, GlobalSignals{.touch = true}};
     }
 
@@ -620,15 +619,15 @@
         !(policy->primaryRangeIsSingleRate() && hasExplicitVoteLayers)) {
         ALOGV("Idle");
         const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending);
-        ATRACE_FORMAT_INSTANT("%s (Idle)", to_string(ranking.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (Idle)", to_string(ranking.front().frameRateMode.fps).c_str());
         return {ranking, GlobalSignals{.idle = true}};
     }
 
     if (layers.empty() || noVoteLayers == layers.size()) {
         ALOGV("No layers with votes");
         const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
-        ATRACE_FORMAT_INSTANT("%s (No layers with votes)",
-                              to_string(ranking.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (No layers with votes)",
+                               to_string(ranking.front().frameRateMode.fps).c_str());
         return {ranking, kNoSignals};
     }
 
@@ -637,8 +636,8 @@
         ALOGV("All layers NoPreference");
         const auto ascendingWithPreferred =
                 rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
-        ATRACE_FORMAT_INSTANT("%s (All layers NoPreference)",
-                              to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (All layers NoPreference)",
+                               to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
         return {ascendingWithPreferred, kNoSignals};
     }
 
@@ -653,8 +652,8 @@
                                                 return !smoothSwitchOnly ||
                                                         mode.modePtr->getId() == activeModeId;
                                             });
-        ATRACE_FORMAT_INSTANT("%s (All layers Min)",
-                              to_string(ranking.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (All layers Min)",
+                               to_string(ranking.front().frameRateMode.fps).c_str());
         return {ranking, kNoSignals};
     }
 
@@ -847,13 +846,13 @@
         if (noLayerScore) {
             ALOGV("Layers not scored");
             const auto descending = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
-            ATRACE_FORMAT_INSTANT("%s (Layers not scored)",
-                                  to_string(descending.front().frameRateMode.fps).c_str());
+            SFTRACE_FORMAT_INSTANT("%s (Layers not scored)",
+                                   to_string(descending.front().frameRateMode.fps).c_str());
             return {descending, kNoSignals};
         } else {
             ALOGV("primaryRangeIsSingleRate");
-            ATRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)",
-                                  to_string(ranking.front().frameRateMode.fps).c_str());
+            SFTRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)",
+                                   to_string(ranking.front().frameRateMode.fps).c_str());
             return {ranking, kNoSignals};
         }
     }
@@ -889,8 +888,8 @@
 
         if (scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
             ALOGV("Touch Boost");
-            ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
-                                  to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
+            SFTRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
+                                   to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
             return {touchRefreshRates, GlobalSignals{.touch = true}};
         }
     }
@@ -901,13 +900,13 @@
         ALOGV("preferredDisplayMode");
         const auto ascendingWithPreferred =
                 rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
-        ATRACE_FORMAT_INSTANT("%s (preferredDisplayMode)",
-                              to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (preferredDisplayMode)",
+                               to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
         return {ascendingWithPreferred, kNoSignals};
     }
 
     ALOGV("%s (scored)", to_string(ranking.front().frameRateMode.fps).c_str());
-    ATRACE_FORMAT_INSTANT("%s (scored)", to_string(ranking.front().frameRateMode.fps).c_str());
+    SFTRACE_FORMAT_INSTANT("%s (scored)", to_string(ranking.front().frameRateMode.fps).c_str());
     return {ranking, kNoSignals};
 }
 
@@ -949,7 +948,7 @@
                                                 Fps displayRefreshRate,
                                                 GlobalSignals globalSignals) const
         -> UidToFrameRateOverride {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Disabled) {
         return {};
     }
@@ -1064,12 +1063,12 @@
                                       return lhs < rhs && !ScoredFrameRate::scoresEqual(lhs, rhs);
                                   });
         ALOGV("%s: overriding to %s for uid=%d", __func__, to_string(overrideFps).c_str(), uid);
-        ATRACE_FORMAT_INSTANT("%s: overriding to %s for uid=%d", __func__,
-                              to_string(overrideFps).c_str(), uid);
+        SFTRACE_FORMAT_INSTANT("%s: overriding to %s for uid=%d", __func__,
+                               to_string(overrideFps).c_str(), uid);
         if (ATRACE_ENABLED() && FlagManager::getInstance().trace_frame_rate_override()) {
             std::stringstream ss;
             ss << "FrameRateOverride " << uid;
-            ATRACE_INT(ss.str().c_str(), overrideFps.getIntValue());
+            SFTRACE_INT(ss.str().c_str(), overrideFps.getIntValue());
         }
         frameRateOverrides.emplace(uid, overrideFps);
     }
@@ -1641,7 +1640,7 @@
         case FrameRateCategory::Normal:
             return FpsRange{60_Hz, 120_Hz};
         case FrameRateCategory::Low:
-            return FpsRange{30_Hz, 120_Hz};
+            return FpsRange{48_Hz, 120_Hz};
         case FrameRateCategory::HighHint:
         case FrameRateCategory::NoPreference:
         case FrameRateCategory::Default:
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 60681a2..c43e942 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -24,12 +24,12 @@
 #include <android-base/stringprintf.h>
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
+#include <common/trace.h>
 #include <configstore/Utils.h>
 #include <ftl/concat.h>
 #include <ftl/enum.h>
 #include <ftl/fake_guard.h>
 #include <ftl/small_map.h>
-#include <gui/TraceUtils.h>
 #include <gui/WindowInfo.h>
 #include <system/window.h>
 #include <ui/DisplayMap.h>
@@ -123,19 +123,22 @@
     promotePacesetterDisplay(pacesetterIdOpt);
 }
 
-void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
+void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+                                PhysicalDisplayId activeDisplayId) {
     auto schedulePtr =
             std::make_shared<VsyncSchedule>(selectorPtr->getActiveMode().modePtr, mFeatures,
                                             [this](PhysicalDisplayId id, bool enable) {
                                                 onHardwareVsyncRequest(id, enable);
                                             });
 
-    registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr));
+    registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr),
+                            activeDisplayId);
 }
 
 void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId,
                                         RefreshRateSelectorPtr selectorPtr,
-                                        VsyncSchedulePtr schedulePtr) {
+                                        VsyncSchedulePtr schedulePtr,
+                                        PhysicalDisplayId activeDisplayId) {
     demotePacesetterDisplay();
 
     auto [pacesetterVsyncSchedule, isNew] = [&]() FTL_FAKE_GUARD(kMainThreadContext) {
@@ -145,7 +148,7 @@
                                                        std::move(schedulePtr), mFeatures)
                                    .second;
 
-        return std::make_pair(promotePacesetterDisplayLocked(), isNew);
+        return std::make_pair(promotePacesetterDisplayLocked(activeDisplayId), isNew);
     }();
 
     applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
@@ -158,7 +161,9 @@
     dispatchHotplug(displayId, Hotplug::Connected);
 }
 
-void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
+void Scheduler::unregisterDisplay(PhysicalDisplayId displayId, PhysicalDisplayId activeDisplayId) {
+    LOG_ALWAYS_FATAL_IF(displayId == activeDisplayId, "Cannot unregister the active display!");
+
     dispatchHotplug(displayId, Hotplug::Disconnected);
 
     demotePacesetterDisplay();
@@ -173,7 +178,7 @@
         // headless virtual display.)
         LOG_ALWAYS_FATAL_IF(mDisplays.empty(), "Cannot unregister all displays!");
 
-        pacesetterVsyncSchedule = promotePacesetterDisplayLocked();
+        pacesetterVsyncSchedule = promotePacesetterDisplayLocked(activeDisplayId);
     }
     applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
 }
@@ -245,8 +250,8 @@
         const auto period = pacesetterPtr->targeterPtr->target().expectedFrameDuration();
         const auto skipDuration = Duration::fromNs(
                 static_cast<nsecs_t>(period.ns() * mPacesetterFrameDurationFractionToSkip));
-        ATRACE_FORMAT("Injecting jank for %f%% of the frame (%" PRId64 " ns)",
-                      mPacesetterFrameDurationFractionToSkip * 100, skipDuration.ns());
+        SFTRACE_FORMAT("Injecting jank for %f%% of the frame (%" PRId64 " ns)",
+                       mPacesetterFrameDurationFractionToSkip * 100, skipDuration.ns());
         std::this_thread::sleep_for(skipDuration);
         mPacesetterFrameDurationFractionToSkip = 0.f;
     }
@@ -277,7 +282,7 @@
         return true;
     }
 
-    ATRACE_FORMAT("%s uid: %d frameRate: %s", __func__, uid, to_string(*frameRate).c_str());
+    SFTRACE_FORMAT("%s uid: %d frameRate: %s", __func__, uid, to_string(*frameRate).c_str());
     return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTime.ns(), *frameRate);
 }
 
@@ -505,7 +510,7 @@
 }
 
 void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mDisplayLock);
     ftl::FakeGuard guard(kMainThreadContext);
 
@@ -539,12 +544,12 @@
 
 void Scheduler::onHardwareVsyncRequest(PhysicalDisplayId id, bool enabled) {
     static const auto& whence = __func__;
-    ATRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str());
+    SFTRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str());
 
     // On main thread to serialize reads/writes of pending hardware VSYNC state.
     static_cast<void>(
             schedule([=, this]() FTL_FAKE_GUARD(mDisplayLock) FTL_FAKE_GUARD(kMainThreadContext) {
-                ATRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str());
+                SFTRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str());
 
                 if (const auto displayOpt = mDisplays.get(id)) {
                     auto& display = displayOpt->get();
@@ -626,7 +631,7 @@
 }
 
 void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr<FenceTime> fence) {
-    ATRACE_NAME(ftl::Concat(__func__, ' ', id.value).c_str());
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', id.value).c_str());
     const auto scheduleOpt =
             (ftl::FakeGuard(mDisplayLock), mDisplays.get(id)).and_then([](const Display& display) {
                 return display.powerMode == hal::PowerMode::OFF
@@ -689,7 +694,7 @@
     const auto selectorPtr = pacesetterSelectorPtr();
     if (!selectorPtr->canSwitch()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     LayerHistory::Summary summary = mLayerHistory.summarize(*selectorPtr, systemTime());
     applyPolicy(&Policy::contentRequirements, std::move(summary));
@@ -774,7 +779,7 @@
 }
 
 void Scheduler::kernelIdleTimerCallback(TimerState state) {
-    ATRACE_INT("ExpiredKernelIdleTimer", static_cast<int>(state));
+    SFTRACE_INT("ExpiredKernelIdleTimer", static_cast<int>(state));
 
     // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
     // magic number
@@ -805,7 +810,7 @@
 
 void Scheduler::idleTimerCallback(TimerState state) {
     applyPolicy(&Policy::idleTimer, state);
-    ATRACE_INT("ExpiredIdleTimer", static_cast<int>(state));
+    SFTRACE_INT("ExpiredIdleTimer", static_cast<int>(state));
 }
 
 void Scheduler::touchTimerCallback(TimerState state) {
@@ -817,12 +822,12 @@
     if (applyPolicy(&Policy::touch, touch).touch) {
         mLayerHistory.clear();
     }
-    ATRACE_INT("TouchState", static_cast<int>(touch));
+    SFTRACE_INT("TouchState", static_cast<int>(touch));
 }
 
 void Scheduler::displayPowerTimerCallback(TimerState state) {
     applyPolicy(&Policy::displayPowerTimer, state);
-    ATRACE_INT("ExpiredDisplayPowerTimer", static_cast<int>(state));
+    SFTRACE_INT("ExpiredDisplayPowerTimer", static_cast<int>(state));
 }
 
 void Scheduler::dump(utils::Dumper& dumper) const {
@@ -985,7 +990,7 @@
     auto& layerChoreographers = choreographers->second;
 
     layerChoreographers.frameRate = fps;
-    ATRACE_FORMAT_INSTANT("%s: %s for %s", __func__, to_string(fps).c_str(), layer.name.c_str());
+    SFTRACE_FORMAT_INSTANT("%s: %s for %s", __func__, to_string(fps).c_str(), layer.name.c_str());
     ALOGV("%s: %s for %s", __func__, to_string(fps).c_str(), layer.name.c_str());
 
     auto it = layerChoreographers.connections.begin();
@@ -1067,13 +1072,13 @@
 
 void Scheduler::updateAttachedChoreographers(
         const surfaceflinger::frontend::LayerHierarchy& layerHierarchy, Fps displayRefreshRate) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     updateAttachedChoreographersInternal(layerHierarchy, displayRefreshRate, 0);
 }
 
 template <typename S, typename T>
 auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::vector<display::DisplayModeRequest> modeRequests;
     GlobalSignals consideredSignals;
 
@@ -1113,8 +1118,10 @@
                                                 .emitEvent = !choice.consideredSignals.idle});
         }
 
-        frameRateOverridesChanged = updateFrameRateOverridesLocked(consideredSignals, modeOpt->fps);
-
+        if (!FlagManager::getInstance().vrr_bugfix_dropped_frame()) {
+            frameRateOverridesChanged =
+                    updateFrameRateOverridesLocked(consideredSignals, modeOpt->fps);
+        }
         if (mPolicy.modeOpt != modeOpt) {
             mPolicy.modeOpt = modeOpt;
             refreshRateChanged = true;
@@ -1129,6 +1136,12 @@
     if (refreshRateChanged) {
         mSchedulerCallback.requestDisplayModes(std::move(modeRequests));
     }
+
+    if (FlagManager::getInstance().vrr_bugfix_dropped_frame()) {
+        std::scoped_lock lock(mPolicyLock);
+        frameRateOverridesChanged =
+                updateFrameRateOverridesLocked(consideredSignals, mPolicy.modeOpt->fps);
+    }
     if (frameRateOverridesChanged) {
         mSchedulerCallback.triggerOnFrameRateOverridesChanged();
     }
@@ -1136,7 +1149,7 @@
 }
 
 auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     DisplayModeChoiceMap modeChoices;
     const auto globalSignals = makeGlobalSignals();
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index ccaa05f..1a4aa79 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -101,9 +101,16 @@
     using ConstVsyncSchedulePtr = std::shared_ptr<const VsyncSchedule>;
     using VsyncSchedulePtr = std::shared_ptr<VsyncSchedule>;
 
-    void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr) REQUIRES(kMainThreadContext)
+    // After registration/unregistration, `activeDisplayId` is promoted to pacesetter. Note that the
+    // active display is never unregistered, since hotplug disconnect never happens for activatable
+    // displays, i.e. a foldable's internal displays or otherwise the (internal or external) primary
+    // display.
+    // TODO: b/255635821 - Remove active display parameters.
+    void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr,
+                         PhysicalDisplayId activeDisplayId) REQUIRES(kMainThreadContext)
             EXCLUDES(mDisplayLock);
-    void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+    void unregisterDisplay(PhysicalDisplayId, PhysicalDisplayId activeDisplayId)
+            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
 
     void run();
 
@@ -390,8 +397,9 @@
     // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit.
     void demotePacesetterDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock);
 
-    void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr)
-            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+    void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr,
+                                 PhysicalDisplayId activeDisplayId) REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock);
 
     struct Policy;
 
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 6d6b70d..8dae3ca 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -19,8 +19,8 @@
 #include <vector>
 
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <ftl/concat.h>
-#include <gui/TraceUtils.h>
 #include <log/log_main.h>
 
 #include <scheduler/TimeKeeper.h>
@@ -52,7 +52,7 @@
     ftl::Concat trace(ftl::truncated<5>(entry.name()), " alarm in ",
                       ns2us(*entry.wakeupTime() - now), "us; VSYNC in ",
                       ns2us(*entry.targetVsync() - now), "us");
-    ATRACE_FORMAT_INSTANT(trace.c_str());
+    SFTRACE_FORMAT_INSTANT(trace.c_str());
 }
 
 } // namespace
@@ -98,7 +98,7 @@
 
 ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing,
                                                       VSyncTracker& tracker, nsecs_t now) {
-    ATRACE_NAME("VSyncDispatchTimerQueueEntry::schedule");
+    SFTRACE_NAME("VSyncDispatchTimerQueueEntry::schedule");
     auto nextVsyncTime =
             tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync,
                                                           now + timing.workDuration +
@@ -110,8 +110,8 @@
             mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance));
     bool const wouldSkipAWakeup =
             mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance)));
-    ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
-                          wouldSkipAVsyncTarget, wouldSkipAWakeup);
+    SFTRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
+                           wouldSkipAVsyncTarget, wouldSkipAWakeup);
     if (FlagManager::getInstance().dont_skip_on_early_ro()) {
         if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
             nextVsyncTime = mArmedInfo->mActualVsyncTime;
@@ -154,13 +154,13 @@
     bool const nextVsyncTooClose = mLastDispatchTime &&
             (nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod;
     if (alreadyDispatchedForVsync) {
-        ATRACE_FORMAT_INSTANT("alreadyDispatchedForVsync");
+        SFTRACE_FORMAT_INSTANT("alreadyDispatchedForVsync");
         return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance,
                                                     *mLastDispatchTime);
     }
 
     if (nextVsyncTooClose) {
-        ATRACE_FORMAT_INSTANT("nextVsyncTooClose");
+        SFTRACE_FORMAT_INSTANT("nextVsyncTooClose");
         return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod,
                                                     *mLastDispatchTime + currentPeriod);
     }
@@ -172,7 +172,7 @@
                                                 VSyncDispatch::ScheduleTiming timing,
                                                 std::optional<ArmingInfo> armedInfo) const
         -> ArmingInfo {
-    ATRACE_NAME("VSyncDispatchTimerQueueEntry::getArmedInfo");
+    SFTRACE_NAME("VSyncDispatchTimerQueueEntry::getArmedInfo");
     const auto earliestReadyBy = now + timing.workDuration + timing.readyDuration;
     const auto earliestVsync = std::max(earliestReadyBy, timing.lastVsync);
 
@@ -188,8 +188,8 @@
                 armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance));
         bool const wouldSkipAWakeup =
                 armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance));
-        ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
-                              wouldSkipAVsyncTarget, wouldSkipAWakeup);
+        SFTRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
+                               wouldSkipAVsyncTarget, wouldSkipAWakeup);
         if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
             return *armedInfo;
         }
@@ -199,7 +199,7 @@
 }
 
 void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) {
-    ATRACE_NAME("VSyncDispatchTimerQueueEntry::update");
+    SFTRACE_NAME("VSyncDispatchTimerQueueEntry::update");
     if (!mArmedInfo && !mWorkloadUpdateInfo) {
         return;
     }
@@ -208,9 +208,9 @@
         const auto workDelta = mWorkloadUpdateInfo->workDuration - mScheduleTiming.workDuration;
         const auto readyDelta = mWorkloadUpdateInfo->readyDuration - mScheduleTiming.readyDuration;
         const auto lastVsyncDelta = mWorkloadUpdateInfo->lastVsync - mScheduleTiming.lastVsync;
-        ATRACE_FORMAT_INSTANT("Workload updated workDelta=%" PRId64 " readyDelta=%" PRId64
-                              " lastVsyncDelta=%" PRId64,
-                              workDelta, readyDelta, lastVsyncDelta);
+        SFTRACE_FORMAT_INSTANT("Workload updated workDelta=%" PRId64 " readyDelta=%" PRId64
+                               " lastVsyncDelta=%" PRId64,
+                               workDelta, readyDelta, lastVsyncDelta);
         mScheduleTiming = *mWorkloadUpdateInfo;
         mWorkloadUpdateInfo.reset();
     }
@@ -310,7 +310,7 @@
 
 void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor(
         nsecs_t now, CallbackMap::const_iterator skipUpdateIt) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::optional<nsecs_t> min;
     std::optional<nsecs_t> targetVsync;
     std::optional<std::string_view> nextWakeupName;
@@ -337,13 +337,13 @@
     if (min && min < mIntendedWakeupTime) {
         setTimer(*min, now);
     } else {
-        ATRACE_NAME("cancel timer");
+        SFTRACE_NAME("cancel timer");
         cancelTimer();
     }
 }
 
 void VSyncDispatchTimerQueue::timerCallback() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     struct Invocation {
         std::shared_ptr<VSyncDispatchTimerQueueEntry> callback;
         nsecs_t vsyncTimestamp;
@@ -383,7 +383,7 @@
 
     for (auto const& invocation : invocations) {
         ftl::Concat trace(ftl::truncated<5>(invocation.callback->name()));
-        ATRACE_FORMAT("%s: %s", __func__, trace.c_str());
+        SFTRACE_FORMAT("%s: %s", __func__, trace.c_str());
         invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp,
                                       invocation.deadlineTimestamp);
     }
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index dd3c4b0..16799bd 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -30,10 +30,10 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <common/FlagManager.h>
+#include <common/trace.h>
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
 #include <ftl/concat.h>
-#include <gui/TraceUtils.h>
 #include <utils/Log.h>
 
 #include "RefreshRateSelector.h"
@@ -77,7 +77,7 @@
 }
 
 inline void VSyncPredictor::traceInt64(const char* name, int64_t value) const {
-    ATRACE_INT64(ftl::Concat(ftl::truncated<14>(name), " ", mId.value).c_str(), value);
+    SFTRACE_INT64(ftl::Concat(ftl::truncated<14>(name), " ", mId.value).c_str(), value);
 }
 
 inline size_t VSyncPredictor::next(size_t i) const {
@@ -98,7 +98,7 @@
             (timestamp - aValidTimestamp) % idealPeriod() * kMaxPercent / idealPeriod();
     if (percent >= kOutlierTolerancePercent &&
         percent <= (kMaxPercent - kOutlierTolerancePercent)) {
-        ATRACE_FORMAT_INSTANT("timestamp is not aligned with model");
+        SFTRACE_FORMAT_INSTANT("timestamp is not aligned with model");
         return false;
     }
 
@@ -109,7 +109,7 @@
     const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / idealPeriod();
     if (distancePercent < kOutlierTolerancePercent) {
         // duplicate timestamp
-        ATRACE_FORMAT_INSTANT("duplicate timestamp");
+        SFTRACE_FORMAT_INSTANT("duplicate timestamp");
         return false;
     }
     return true;
@@ -135,7 +135,7 @@
 }
 
 bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard lock(mMutex);
 
@@ -155,8 +155,8 @@
         } else {
             mKnownTimestamp = timestamp;
         }
-        ATRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago",
-                              (mClock->now() - *mKnownTimestamp) / 1e6f);
+        SFTRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago",
+                               (mClock->now() - *mKnownTimestamp) / 1e6f);
         return false;
     }
 
@@ -297,7 +297,7 @@
 
 nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
                                                      std::optional<nsecs_t> lastVsyncOpt) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard lock(mMutex);
 
     const auto now = TimePoint::fromNs(mClock->now());
@@ -330,8 +330,8 @@
 
     if (*vsyncOpt > mLastCommittedVsync) {
         mLastCommittedVsync = *vsyncOpt;
-        ATRACE_FORMAT_INSTANT("mLastCommittedVsync in %.2fms",
-                              float(mLastCommittedVsync.ns() - mClock->now()) / 1e6f);
+        SFTRACE_FORMAT_INSTANT("mLastCommittedVsync in %.2fms",
+                               float(mLastCommittedVsync.ns() - mClock->now()) / 1e6f);
     }
 
     return vsyncOpt->ns();
@@ -360,7 +360,11 @@
     purgeTimelines(now);
 
     for (auto& timeline : mTimelines) {
-        if (timeline.validUntil() && timeline.validUntil()->ns() > vsync) {
+        const bool isVsyncValid = FlagManager::getInstance().vrr_bugfix_24q4()
+                ? timeline.isWithin(TimePoint::fromNs(vsync)) ==
+                        VsyncTimeline::VsyncOnTimeline::Unique
+                : timeline.validUntil() && timeline.validUntil()->ns() > vsync;
+        if (isVsyncValid) {
             return timeline.isVSyncInPhase(model, vsync, frameRate);
         }
     }
@@ -370,7 +374,7 @@
 }
 
 void VSyncPredictor::setRenderRate(Fps renderRate, bool applyImmediately) {
-    ATRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str());
+    SFTRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str());
     ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str());
     std::lock_guard lock(mMutex);
     const auto prevRenderRate = mRenderRateOpt;
@@ -378,7 +382,7 @@
     const auto renderPeriodDelta =
             prevRenderRate ? prevRenderRate->getPeriodNsecs() - renderRate.getPeriodNsecs() : 0;
     if (applyImmediately) {
-        ATRACE_FORMAT_INSTANT("applyImmediately");
+        SFTRACE_FORMAT_INSTANT("applyImmediately");
         while (mTimelines.size() > 1) {
             mTimelines.pop_front();
         }
@@ -390,13 +394,19 @@
     const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() &&
             mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs();
     if (newRenderRateIsHigher) {
-        ATRACE_FORMAT_INSTANT("newRenderRateIsHigher");
+        SFTRACE_FORMAT_INSTANT("newRenderRateIsHigher");
         mTimelines.clear();
         mLastCommittedVsync = TimePoint::fromNs(0);
 
     } else {
-        mTimelines.back().freeze(
-                TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
+        if (FlagManager::getInstance().vrr_bugfix_24q4()) {
+            // We need to freeze the timeline at the committed vsync so that we don't
+            // overshoot the deadline.
+            mTimelines.back().freeze(mLastCommittedVsync);
+        } else {
+            mTimelines.back().freeze(
+                    TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
+        }
     }
     mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, renderRate);
     purgeTimelines(TimePoint::fromNs(mClock->now()));
@@ -405,7 +415,7 @@
 void VSyncPredictor::setDisplayModePtr(ftl::NonNull<DisplayModePtr> modePtr) {
     LOG_ALWAYS_FATAL_IF(mId != modePtr->getPhysicalDisplayId(),
                         "mode does not belong to the display");
-    ATRACE_FORMAT("%s %s", __func__, to_string(*modePtr).c_str());
+    SFTRACE_FORMAT("%s %s", __func__, to_string(*modePtr).c_str());
     const auto timeout = modePtr->getVrrConfig()
             ? modePtr->getVrrConfig()->notifyExpectedPresentConfig
             : std::nullopt;
@@ -433,7 +443,7 @@
 
 Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
                                                       TimePoint lastConfirmedPresentTime) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (mNumVsyncsForFrame <= 1) {
         return 0ns;
@@ -446,14 +456,15 @@
     auto prev = lastConfirmedPresentTime.ns();
     for (auto& current : mPastExpectedPresentTimes) {
         if (CC_UNLIKELY(mTraceOn)) {
-            ATRACE_FORMAT_INSTANT("current %.2f past last signaled fence",
-                                  static_cast<float>(current.ns() - lastConfirmedPresentTime.ns()) /
-                                          1e6f);
+            SFTRACE_FORMAT_INSTANT("current %.2f past last signaled fence",
+                                   static_cast<float>(current.ns() -
+                                                      lastConfirmedPresentTime.ns()) /
+                                           1e6f);
         }
 
         const auto minPeriodViolation = current.ns() - prev + threshold < minFramePeriod;
         if (minPeriodViolation) {
-            ATRACE_NAME("minPeriodViolation");
+            SFTRACE_NAME("minPeriodViolation");
             current = TimePoint::fromNs(prev + minFramePeriod);
             prev = current.ns();
         } else {
@@ -477,16 +488,16 @@
 
 void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime,
                                   TimePoint lastConfirmedPresentTime) {
-    ATRACE_NAME("VSyncPredictor::onFrameBegin");
+    SFTRACE_NAME("VSyncPredictor::onFrameBegin");
     std::lock_guard lock(mMutex);
 
     if (!mDisplayModePtr->getVrrConfig()) return;
 
     if (CC_UNLIKELY(mTraceOn)) {
-        ATRACE_FORMAT_INSTANT("vsync is %.2f past last signaled fence",
-                              static_cast<float>(expectedPresentTime.ns() -
-                                                 lastConfirmedPresentTime.ns()) /
-                                      1e6f);
+        SFTRACE_FORMAT_INSTANT("vsync is %.2f past last signaled fence",
+                               static_cast<float>(expectedPresentTime.ns() -
+                                                  lastConfirmedPresentTime.ns()) /
+                                       1e6f);
     }
     const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
     const auto threshold = currentPeriod / 2;
@@ -497,9 +508,9 @@
         const bool frontIsBeforeConfirmed = front < lastConfirmedPresentTime.ns() + threshold;
         if (frontIsBeforeConfirmed) {
             if (CC_UNLIKELY(mTraceOn)) {
-                ATRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence",
-                                      static_cast<float>(lastConfirmedPresentTime.ns() - front) /
-                                              1e6f);
+                SFTRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence",
+                                       static_cast<float>(lastConfirmedPresentTime.ns() - front) /
+                                               1e6f);
             }
             mPastExpectedPresentTimes.pop_front();
         } else {
@@ -514,7 +525,7 @@
 }
 
 void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) {
-    ATRACE_NAME("VSyncPredictor::onFrameMissed");
+    SFTRACE_NAME("VSyncPredictor::onFrameMissed");
 
     std::lock_guard lock(mMutex);
     if (!mDisplayModePtr->getVrrConfig()) return;
@@ -540,7 +551,7 @@
 }
 
 void VSyncPredictor::clearTimestamps() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!mTimestamps.empty()) {
         auto const maxRb = *std::max_element(mTimestamps.begin(), mTimestamps.end());
@@ -602,7 +613,7 @@
     if (mRenderRateOpt &&
         mLastCommittedVsync.ns() + mRenderRateOpt->getPeriodNsecs() * kEnoughFramesToBreakPhase <
                 mClock->now()) {
-        ATRACE_FORMAT_INSTANT("kEnoughFramesToBreakPhase");
+        SFTRACE_FORMAT_INSTANT("kEnoughFramesToBreakPhase");
         mTimelines.clear();
         mLastCommittedVsync = TimePoint::fromNs(0);
         mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
@@ -611,7 +622,10 @@
 
     while (mTimelines.size() > 1) {
         const auto validUntilOpt = mTimelines.front().validUntil();
-        if (validUntilOpt && *validUntilOpt < now) {
+        const bool isTimelineOutDated = FlagManager::getInstance().vrr_bugfix_24q4()
+                ? mTimelines.front().isWithin(now) == VsyncTimeline::VsyncOnTimeline::Outside
+                : validUntilOpt && *validUntilOpt < now;
+        if (isTimelineOutDated) {
             mTimelines.pop_front();
         } else {
             break;
@@ -635,16 +649,16 @@
 
 void VSyncPredictor::VsyncTimeline::freeze(TimePoint lastVsync) {
     LOG_ALWAYS_FATAL_IF(mValidUntil.has_value());
-    ATRACE_FORMAT_INSTANT("renderRate %s valid for %.2f",
-                          mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA",
-                          float(lastVsync.ns() - TimePoint::now().ns()) / 1e6f);
+    SFTRACE_FORMAT_INSTANT("renderRate %s valid for %.2f",
+                           mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA",
+                           float(lastVsync.ns() - TimePoint::now().ns()) / 1e6f);
     mValidUntil = lastVsync;
 }
 
 std::optional<TimePoint> VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTimeFrom(
         Model model, std::optional<Period> minFramePeriodOpt, nsecs_t vsync,
         MissedVsync missedVsync, std::optional<nsecs_t> lastVsyncOpt) {
-    ATRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA");
+    SFTRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA");
 
     nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync);
     const auto threshold = model.slope / 2;
@@ -658,32 +672,38 @@
             // on whether we skipped the frame (onFrameMissed) or not (onFrameBegin) we apply a
             // different fixup. There is no need to to shift the vsync timeline again.
             vsyncTime += missedVsync.fixup.ns();
-            ATRACE_FORMAT_INSTANT("lastFrameMissed");
+            SFTRACE_FORMAT_INSTANT("lastFrameMissed");
         } else if (mightBackpressure && lastVsyncOpt) {
-            // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it
-            // first before trying to use it.
-            lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
+            if (!FlagManager::getInstance().vrr_bugfix_24q4()) {
+                // lastVsyncOpt does not need to be corrected with the new rate, and
+                // it should be used as is to avoid skipping a frame when changing rates are
+                // aligned at vsync time.
+                lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
+            }
             const auto vsyncDiff = vsyncTime - *lastVsyncOpt;
             if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) {
                 // avoid a duplicate vsync
-                ATRACE_FORMAT_INSTANT("skipping a vsync to avoid duplicate frame. next in %.2f "
-                                      "which "
-                                      "is %.2f "
-                                      "from "
-                                      "prev. "
-                                      "adjust by %.2f",
-                                      static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f,
-                                      static_cast<float>(vsyncDiff) / 1e6f,
-                                      static_cast<float>(mRenderRateOpt->getPeriodNsecs()) / 1e6f);
+                SFTRACE_FORMAT_INSTANT("skipping a vsync to avoid duplicate frame. next in %.2f "
+                                       "which "
+                                       "is %.2f "
+                                       "from "
+                                       "prev. "
+                                       "adjust by %.2f",
+                                       static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f,
+                                       static_cast<float>(vsyncDiff) / 1e6f,
+                                       static_cast<float>(mRenderRateOpt->getPeriodNsecs()) / 1e6f);
                 vsyncTime += mRenderRateOpt->getPeriodNsecs();
             }
         }
     }
 
-    ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f);
-    if (mValidUntil && vsyncTime > mValidUntil->ns()) {
-        ATRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f",
-                              static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f);
+    SFTRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f);
+    const bool isVsyncInvalid = FlagManager::getInstance().vrr_bugfix_24q4()
+            ? isWithin(TimePoint::fromNs(vsyncTime)) == VsyncOnTimeline::Outside
+            : mValidUntil && vsyncTime > mValidUntil->ns();
+    if (isVsyncInvalid) {
+        SFTRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f",
+                               static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f);
         return std::nullopt;
     }
 
@@ -747,14 +767,14 @@
         return true;
     }
     const auto vsyncSequence = getVsyncSequenceLocked(model, vsync);
-    ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64 " divisor: %zu",
-                          getVsyncIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq, divisor);
+    SFTRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64 " divisor: %zu",
+                           getVsyncIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq, divisor);
     return vsyncSequence.seq % divisor == 0;
 }
 
 void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase) {
     if (mLastVsyncSequence) {
-        ATRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast<float>(phase.ns()) / 1e6f);
+        SFTRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast<float>(phase.ns()) / 1e6f);
         mLastVsyncSequence->vsyncTime += phase.ns();
     }
 }
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 8ce61d8..66a7d71 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -106,6 +106,24 @@
         void shiftVsyncSequence(Duration phase);
         void setRenderRate(std::optional<Fps> renderRateOpt) { mRenderRateOpt = renderRateOpt; }
 
+        enum class VsyncOnTimeline {
+            Unique,  // Within timeline, not shared with next timeline.
+            Shared,  // Within timeline, shared with next timeline.
+            Outside, // Outside of the timeline.
+        };
+        VsyncOnTimeline isWithin(TimePoint vsync) {
+            const auto threshold = mIdealPeriod.ns() / 2;
+            if (!mValidUntil || vsync.ns() < mValidUntil->ns() - threshold) {
+                // if mValidUntil is absent then timeline is not frozen and
+                // vsync should be unique to that timeline.
+                return VsyncOnTimeline::Unique;
+            }
+            if (vsync.ns() > mValidUntil->ns() + threshold) {
+                return VsyncOnTimeline::Outside;
+            }
+            return VsyncOnTimeline::Shared;
+        }
+
     private:
         nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync);
         VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync);
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 8038364..2455822 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -20,11 +20,10 @@
 //#define LOG_NDEBUG 0
 
 #include <assert.h>
+#include <common/trace.h>
 #include <cutils/properties.h>
 #include <ftl/concat.h>
-#include <gui/TraceUtils.h>
 #include <log/log.h>
-#include <utils/Trace.h>
 
 #include "../TracedOrdinal.h"
 #include "VSyncDispatch.h"
@@ -53,7 +52,7 @@
 VSyncReactor::~VSyncReactor() = default;
 
 bool VSyncReactor::addPresentFence(std::shared_ptr<FenceTime> fence) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!fence) {
         return false;
@@ -66,8 +65,8 @@
 
     std::lock_guard lock(mMutex);
     if (mExternalIgnoreFences || mInternalIgnoreFences) {
-        ATRACE_FORMAT_INSTANT("mExternalIgnoreFences=%d mInternalIgnoreFences=%d",
-            mExternalIgnoreFences, mInternalIgnoreFences);
+        SFTRACE_FORMAT_INSTANT("mExternalIgnoreFences=%d mInternalIgnoreFences=%d",
+                               mExternalIgnoreFences, mInternalIgnoreFences);
         return true;
     }
 
@@ -121,7 +120,7 @@
 }
 
 void VSyncReactor::startPeriodTransitionInternal(ftl::NonNull<DisplayModePtr> modePtr) {
-    ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
+    SFTRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
     mPeriodConfirmationInProgress = true;
     mModePtrTransitioningTo = modePtr.get();
     mMoreSamplesNeeded = true;
@@ -129,15 +128,15 @@
 }
 
 void VSyncReactor::endPeriodTransition() {
-    ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
+    SFTRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
     mModePtrTransitioningTo.reset();
     mPeriodConfirmationInProgress = false;
     mLastHwVsync.reset();
 }
 
 void VSyncReactor::onDisplayModeChanged(ftl::NonNull<DisplayModePtr> modePtr, bool force) {
-    ATRACE_INT64(ftl::Concat("VSR-", __func__, " ", mId.value).c_str(),
-                 modePtr->getVsyncRate().getPeriodNsecs());
+    SFTRACE_INT64(ftl::Concat("VSR-", __func__, " ", mId.value).c_str(),
+                  modePtr->getVsyncRate().getPeriodNsecs());
     std::lock_guard lock(mMutex);
     mLastHwVsync.reset();
 
@@ -191,7 +190,7 @@
 
     std::lock_guard lock(mMutex);
     if (periodConfirmed(timestamp, hwcVsyncPeriod)) {
-        ATRACE_FORMAT("VSR %" PRIu64 ": period confirmed", mId.value);
+        SFTRACE_FORMAT("VSR %" PRIu64 ": period confirmed", mId.value);
         if (mModePtrTransitioningTo) {
             mTracker.setDisplayModePtr(ftl::as_non_null(mModePtrTransitioningTo));
             *periodFlushed = true;
@@ -205,12 +204,12 @@
         endPeriodTransition();
         mMoreSamplesNeeded = mTracker.needsMoreSamples();
     } else if (mPeriodConfirmationInProgress) {
-        ATRACE_FORMAT("VSR %" PRIu64 ": still confirming period", mId.value);
+        SFTRACE_FORMAT("VSR %" PRIu64 ": still confirming period", mId.value);
         mLastHwVsync = timestamp;
         mMoreSamplesNeeded = true;
         *periodFlushed = false;
     } else {
-        ATRACE_FORMAT("VSR %" PRIu64 ": adding sample", mId.value);
+        SFTRACE_FORMAT("VSR %" PRIu64 ": adding sample", mId.value);
         *periodFlushed = false;
         mTracker.addVsyncTimestamp(timestamp);
         mMoreSamplesNeeded = mTracker.needsMoreSamples();
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.cpp b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
index 586357f..fa377e9 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
@@ -22,8 +22,8 @@
 #include "VsyncModulator.h"
 
 #include <android-base/properties.h>
+#include <common/trace.h>
 #include <log/log.h>
-#include <utils/Trace.h>
 
 #include <chrono>
 #include <cinttypes>
@@ -72,7 +72,7 @@
     }
 
     if (mTraceDetailedInfo) {
-        ATRACE_INT("mEarlyWakeup", static_cast<int>(mEarlyWakeupRequests.size()));
+        SFTRACE_INT("mEarlyWakeup", static_cast<int>(mEarlyWakeupRequests.size()));
     }
 
     if (mEarlyWakeupRequests.empty() && schedule == Schedule::EarlyEnd) {
@@ -172,9 +172,9 @@
         const bool isEarlyGpu = &offsets == &mVsyncConfigSet.earlyGpu;
         const bool isLate = &offsets == &mVsyncConfigSet.late;
 
-        ATRACE_INT("Vsync-EarlyOffsetsOn", isEarly);
-        ATRACE_INT("Vsync-EarlyGpuOffsetsOn", isEarlyGpu);
-        ATRACE_INT("Vsync-LateOffsetsOn", isLate);
+        SFTRACE_INT("Vsync-EarlyOffsetsOn", isEarly);
+        SFTRACE_INT("Vsync-EarlyGpuOffsetsOn", isEarlyGpu);
+        SFTRACE_INT("Vsync-LateOffsetsOn", isLate);
     }
 
     return offsets;
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index 2fa3318..d3e312a 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -18,8 +18,8 @@
 
 #include <common/FlagManager.h>
 
+#include <common/trace.h>
 #include <ftl/fake_guard.h>
-#include <gui/TraceUtils.h>
 #include <scheduler/Fps.h>
 #include <scheduler/Timer.h>
 
@@ -182,7 +182,7 @@
 }
 
 void VsyncSchedule::enableHardwareVsyncLocked() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mHwVsyncState == HwVsyncState::Disabled) {
         getTracker().resetModel();
         mRequestHardwareVsync(mId, true);
@@ -191,7 +191,7 @@
 }
 
 void VsyncSchedule::disableHardwareVsync(bool disallow) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard<std::mutex> lock(mHwVsyncLock);
     switch (mHwVsyncState) {
         case HwVsyncState::Enabled:
diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
index badd21e..60694b9 100644
--- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-#include <gui/TraceUtils.h>
-
 #include <common/FlagManager.h>
+#include <common/trace.h>
 #include <scheduler/FrameTargeter.h>
 #include <scheduler/IVsyncSource.h>
 
@@ -90,9 +89,9 @@
         mEarliestPresentTime = computeEarliestPresentTime(minFramePeriod, args.hwcMinWorkDuration);
     }
 
-    ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, ftl::to_underlying(args.vsyncId),
-                  ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()),
-                  mExpectedPresentTime == args.expectedVsyncTime ? "" : " (adjusted)");
+    SFTRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, ftl::to_underlying(args.vsyncId),
+                   ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()),
+                   mExpectedPresentTime == args.expectedVsyncTime ? "" : " (adjusted)");
 
     const FenceTimePtr& pastPresentFence = presentFenceForPastVsync(minFramePeriod);
 
@@ -165,7 +164,7 @@
 }
 
 bool FrameTargeter::isFencePending(const FenceTimePtr& fence, int graceTimeMs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     const status_t status = fence->wait(graceTimeMs);
 
     // This is the same as Fence::Status::Unsignaled, but it saves a call to getStatus,
diff --git a/services/surfaceflinger/Scheduler/src/Timer.cpp b/services/surfaceflinger/Scheduler/src/Timer.cpp
index eeb9c60..fba3d58 100644
--- a/services/surfaceflinger/Scheduler/src/Timer.cpp
+++ b/services/surfaceflinger/Scheduler/src/Timer.cpp
@@ -24,10 +24,10 @@
 #include <sys/timerfd.h>
 #include <sys/unistd.h>
 
+#include <common/trace.h>
 #include <ftl/concat.h>
 #include <ftl/enum.h>
 #include <log/log.h>
-#include <utils/Trace.h>
 
 #include <scheduler/Timer.h>
 
@@ -190,7 +190,7 @@
         setDebugState(DebugState::Running);
         if (ATRACE_ENABLED()) {
             ftl::Concat trace("TimerIteration #", iteration++);
-            ATRACE_NAME(trace.c_str());
+            SFTRACE_NAME(trace.c_str());
         }
 
         if (nfds == -1) {
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 596ec12..d00e762 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -42,6 +42,7 @@
 #include <binder/PermissionCache.h>
 #include <com_android_graphics_surfaceflinger_flags.h>
 #include <common/FlagManager.h>
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/Display.h>
@@ -71,7 +72,6 @@
 #include <gui/LayerState.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
-#include <gui/TraceUtils.h>
 #include <hidl/ServiceManagement.h>
 #include <layerproto/LayerProtoParser.h>
 #include <linux/sched/types.h>
@@ -143,6 +143,7 @@
 #include "FrontEnd/LayerLog.h"
 #include "FrontEnd/LayerSnapshot.h"
 #include "HdrLayerInfoReporter.h"
+#include "Jank/JankTracker.h"
 #include "Layer.h"
 #include "LayerProtoHelper.h"
 #include "LayerRenderArea.h"
@@ -433,7 +434,7 @@
 }
 
 SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipInitialization) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGI("SurfaceFlinger is starting");
 
     hasSyncFramework = running_without_sync_framework(true);
@@ -860,7 +861,7 @@
 }
 
 void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGI(  "SurfaceFlinger's main thread ready to run. "
             "Initializing graphics H/W...");
     addTransactionReadyFilters();
@@ -1312,7 +1313,7 @@
     const auto mode = desiredMode.mode;
     const auto displayId = mode.modePtr->getPhysicalDisplayId();
 
-    ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
     const bool emitEvent = desiredMode.emitEvent;
 
@@ -1365,7 +1366,7 @@
 
 status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<display::DisplayToken>& displayToken,
                                                    DisplayModeId modeId, Fps minFps, Fps maxFps) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!displayToken) {
         return BAD_VALUE;
@@ -1415,7 +1416,7 @@
 }
 
 void SurfaceFlinger::finalizeDisplayModeChange(PhysicalDisplayId displayId) {
-    ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
     const auto pendingModeOpt = mDisplayModeController.getPendingMode(displayId);
     if (!pendingModeOpt) {
@@ -1479,9 +1480,7 @@
 }
 
 void SurfaceFlinger::initiateDisplayModeChanges() {
-    ATRACE_CALL();
-
-    std::optional<PhysicalDisplayId> displayToUpdateImmediately;
+    SFTRACE_CALL();
 
     for (const auto& [displayId, physical] : FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays)) {
         auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId);
@@ -1536,21 +1535,14 @@
         if (outTimeline.refreshRequired) {
             scheduleComposite(FrameHint::kNone);
         } else {
-            // TODO(b/255635711): Remove `displayToUpdateImmediately` to `finalizeDisplayModeChange`
-            // for all displays. This was only needed when the loop iterated over `mDisplays` rather
-            // than `mPhysicalDisplays`.
-            displayToUpdateImmediately = displayId;
-        }
-    }
+            // HWC has requested to apply the mode change immediately rather than on the next frame.
+            finalizeDisplayModeChange(displayId);
 
-    if (displayToUpdateImmediately) {
-        const auto displayId = *displayToUpdateImmediately;
-        finalizeDisplayModeChange(displayId);
-
-        const auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId);
-        if (desiredModeOpt &&
-            mDisplayModeController.getActiveMode(displayId) == desiredModeOpt->mode) {
-            applyActiveMode(displayId);
+            const auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId);
+            if (desiredModeOpt &&
+                mDisplayModeController.getActiveMode(displayId) == desiredModeOpt->mode) {
+                applyActiveMode(displayId);
+            }
         }
     }
 }
@@ -1558,7 +1550,7 @@
 void SurfaceFlinger::disableExpensiveRendering() {
     const char* const whence = __func__;
     auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
-        ATRACE_NAME(whence);
+        SFTRACE_NAME(whence);
         if (mPowerAdvisor->isUsingExpensiveRendering()) {
             for (const auto& [_, display] : mDisplays) {
                 constexpr bool kDisable = false;
@@ -2223,9 +2215,9 @@
         }
     }
 
-    ATRACE_NAME(vsyncPeriod
-                        ? ftl::Concat(__func__, ' ', hwcDisplayId, ' ', *vsyncPeriod, "ns").c_str()
-                        : ftl::Concat(__func__, ' ', hwcDisplayId).c_str());
+    SFTRACE_NAME(vsyncPeriod
+                         ? ftl::Concat(__func__, ' ', hwcDisplayId, ' ', *vsyncPeriod, "ns").c_str()
+                         : ftl::Concat(__func__, ' ', hwcDisplayId).c_str());
 
     Mutex::Autolock lock(mStateLock);
     if (const auto displayIdOpt = getHwComposer().onVsync(hwcDisplayId, timestamp)) {
@@ -2283,12 +2275,12 @@
 }
 
 void SurfaceFlinger::onComposerHalVsyncIdle(hal::HWDisplayId) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mScheduler->forceNextResync();
 }
 
 void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData& data) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     const char* const whence = __func__;
     static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
                                                    kMainThreadContext) {
@@ -2297,7 +2289,7 @@
                 const Fps refreshRate = Fps::fromPeriodNsecs(
                         getHwComposer().getComposer()->isVrrSupported() ? data.refreshPeriodNanos
                                                                         : data.vsyncPeriodNanos);
-                ATRACE_FORMAT("%s refresh rate = %d", whence, refreshRate.getIntValue());
+                SFTRACE_FORMAT("%s refresh rate = %d", whence, refreshRate.getIntValue());
 
                 const auto renderRate = mDisplayModeController.getActiveMode(*displayIdOpt).fps;
                 constexpr bool kSetByHwc = true;
@@ -2317,7 +2309,7 @@
 bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTimeNs,
                                                 bool flushTransactions,
                                                 bool& outTransactionsAreEmpty) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     frontend::Update update;
     if (flushTransactions) {
         update = flushLifecycleUpdates();
@@ -2416,10 +2408,10 @@
 bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs,
                                           bool flushTransactions, bool& outTransactionsAreEmpty) {
     using Changes = frontend::RequestedLayerState::Changes;
-    ATRACE_CALL();
+    SFTRACE_CALL();
     frontend::Update update;
     if (flushTransactions) {
-        ATRACE_NAME("TransactionHandler:flushTransactions");
+        SFTRACE_NAME("TransactionHandler:flushTransactions");
         // Locking:
         // 1. to prevent onHandleDestroyed from being called while the state lock is held,
         // we must keep a copy of the transactions (specifically the composer
@@ -2471,7 +2463,7 @@
     mustComposite |= applyAndCommitDisplayTransactionStatesLocked(update.transactions);
 
     {
-        ATRACE_NAME("LayerSnapshotBuilder:update");
+        SFTRACE_NAME("LayerSnapshotBuilder:update");
         frontend::LayerSnapshotBuilder::Args
                 args{.root = mLayerHierarchyBuilder.getHierarchy(),
                      .layerLifecycleManager = mLayerLifecycleManager,
@@ -2512,7 +2504,7 @@
     }
 
     bool newDataLatched = false;
-    ATRACE_NAME("DisplayCallbackAndStatsUpdates");
+    SFTRACE_NAME("DisplayCallbackAndStatsUpdates");
     mustComposite |= applyTransactionsLocked(update.transactions, vsyncId);
     traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); });
     const nsecs_t latchTime = systemTime();
@@ -2578,7 +2570,7 @@
     }
 
     {
-        ATRACE_NAME("LLM:commitChanges");
+        SFTRACE_NAME("LLM:commitChanges");
         mLayerLifecycleManager.commitChanges();
     }
 
@@ -2601,7 +2593,7 @@
     const scheduler::FrameTarget& pacesetterFrameTarget = *frameTargets.get(pacesetterId)->get();
 
     const VsyncId vsyncId = pacesetterFrameTarget.vsyncId();
-    ATRACE_NAME(ftl::Concat(__func__, ' ', ftl::to_underlying(vsyncId)).c_str());
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', ftl::to_underlying(vsyncId)).c_str());
 
     if (pacesetterFrameTarget.didMissFrame()) {
         mTimeStats->incrementMissedFrames();
@@ -2743,7 +2735,7 @@
             frameTargeters.get(pacesetterId)->get()->target();
 
     const VsyncId vsyncId = pacesetterTarget.vsyncId();
-    ATRACE_NAME(ftl::Concat(__func__, ' ', ftl::to_underlying(vsyncId)).c_str());
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', ftl::to_underlying(vsyncId)).c_str());
 
     compositionengine::CompositionRefreshArgs refreshArgs;
     refreshArgs.powerCallback = this;
@@ -2907,6 +2899,7 @@
         }
     }
 
+    SFTRACE_NAME("postComposition");
     mTimeStats->recordFrameDuration(pacesetterTarget.frameBeginTime().ns(), systemTime());
 
     // Send a power hint after presentation is finished.
@@ -3002,7 +2995,7 @@
 }
 
 void SurfaceFlinger::updateLayerGeometry() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (mVisibleRegionsDirty) {
         computeLayerBounds();
@@ -3086,7 +3079,7 @@
 void SurfaceFlinger::onCompositionPresented(PhysicalDisplayId pacesetterId,
                                             const scheduler::FrameTargeters& frameTargeters,
                                             nsecs_t presentStartTime) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     ui::PhysicalDisplayMap<PhysicalDisplayId, std::shared_ptr<FenceTime>> presentFences;
     ui::PhysicalDisplayMap<PhysicalDisplayId, const sp<Fence>> gpuCompositionDoneFences;
@@ -3331,7 +3324,7 @@
     // side-effect of getTotalSize(), so we check that again here
     if (ATRACE_ENABLED()) {
         // getTotalSize returns the total number of buffers that were allocated by SurfaceFlinger
-        ATRACE_INT64("Total Buffer Size", GraphicBufferAllocator::get().getTotalSize());
+        SFTRACE_INT64("Total Buffer Size", GraphicBufferAllocator::get().getTotalSize());
     }
 
     logFrameStats(presentTime);
@@ -3368,7 +3361,7 @@
 }
 
 void SurfaceFlinger::commitTransactions() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mDebugInTransaction = systemTime();
 
     // Here we're guaranteed that some transaction flags are set
@@ -3381,7 +3374,7 @@
 }
 
 void SurfaceFlinger::commitTransactionsLegacy() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     // Keep a copy of the drawing state (that is going to be overwritten
     // by commitTransactionsLocked) outside of mStateLock so that the side
@@ -3855,7 +3848,8 @@
         ftl::FakeGuard guard(kMainThreadContext);
 
         // For hotplug reconnect, renew the registration since display modes have been reloaded.
-        mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
+        mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector(),
+                                    mActiveDisplayId);
     }
 
     if (display->isVirtual()) {
@@ -3894,7 +3888,7 @@
         if (display->isVirtual()) {
             releaseVirtualDisplay(display->getVirtualId());
         } else {
-            mScheduler->unregisterDisplay(display->getPhysicalId());
+            mScheduler->unregisterDisplay(display->getPhysicalId(), mActiveDisplayId);
         }
     }
 
@@ -4128,7 +4122,7 @@
     if (!mInputFlinger || (!mUpdateInputInfo && mInputWindowCommands.empty())) {
         return;
     }
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::vector<WindowInfo> windowInfos;
     std::vector<DisplayInfo> displayInfos;
@@ -4158,7 +4152,7 @@
                                                               std::move(mInputWindowCommands),
                                                       inputFlinger = mInputFlinger, this,
                                                       visibleWindowsChanged, vsyncId, frameTime]() {
-        ATRACE_NAME("BackgroundExecutor::updateInputFlinger");
+        SFTRACE_NAME("BackgroundExecutor::updateInputFlinger");
         if (updateWindowInfo) {
             mWindowInfosListenerInvoker
                     ->windowInfosChanged(gui::WindowInfosUpdate{std::move(windowInfos),
@@ -4271,7 +4265,7 @@
         return;
     }
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     // If this is called from the main thread mStateLock must be locked before
     // Currently the only way to call this function from the main thread is from
@@ -4310,7 +4304,7 @@
 }
 
 void SurfaceFlinger::onChoreographerAttached() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mLayerLifecycleManagerEnabled) {
         mUpdateAttachedChoreographer = true;
         scheduleCommit(FrameHint::kNone);
@@ -4506,7 +4500,8 @@
                                              getFactory(), activeRefreshRate, *mTimeStats);
 
     // The pacesetter must be registered before EventThread creation below.
-    mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
+    mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector(),
+                                mActiveDisplayId);
     if (FlagManager::getInstance().vrr_config()) {
         mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps,
                                   /*applyImmediately*/ true);
@@ -4537,7 +4532,7 @@
 }
 
 void SurfaceFlinger::doCommitTransactions() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!mLayersPendingRemoval.isEmpty()) {
         // Notify removed layers now that they can't be drawn from
@@ -4618,7 +4613,7 @@
 }
 
 bool SurfaceFlinger::latchBuffers() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     const nsecs_t latchTime = systemTime();
 
@@ -4795,7 +4790,7 @@
 
 uint32_t SurfaceFlinger::clearTransactionFlags(uint32_t mask) {
     uint32_t transactionFlags = mTransactionFlags.fetch_and(~mask);
-    ATRACE_INT("mTransactionFlags", transactionFlags);
+    SFTRACE_INT("mTransactionFlags", transactionFlags);
     return transactionFlags & mask;
 }
 
@@ -4803,7 +4798,7 @@
                                          const sp<IBinder>& applyToken, FrameHint frameHint) {
     mScheduler->modulateVsync({}, &VsyncModulator::setTransactionSchedule, schedule, applyToken);
     uint32_t transactionFlags = mTransactionFlags.fetch_or(mask);
-    ATRACE_INT("mTransactionFlags", transactionFlags);
+    SFTRACE_INT("mTransactionFlags", transactionFlags);
 
     if (const bool scheduled = transactionFlags & mask; !scheduled) {
         scheduleCommit(frameHint);
@@ -4828,8 +4823,8 @@
     // for stability reasons.
     if (!transaction.isAutoTimestamp && desiredPresentTime >= expectedPresentTime &&
         desiredPresentTime < expectedPresentTime + 1s) {
-        ATRACE_FORMAT("not current desiredPresentTime: %" PRId64 " expectedPresentTime: %" PRId64,
-                      desiredPresentTime, expectedPresentTime);
+        SFTRACE_FORMAT("not current desiredPresentTime: %" PRId64 " expectedPresentTime: %" PRId64,
+                       desiredPresentTime, expectedPresentTime);
         return TransactionReadiness::NotReady;
     }
 
@@ -4841,16 +4836,16 @@
     // incorrectly as the frame rate of SF changed before it drained the older transactions.
     if (ftl::to_underlying(vsyncId) == FrameTimelineInfo::INVALID_VSYNC_ID &&
         !mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) {
-        ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", expectedPresentTime,
-                      transaction.originUid);
+        SFTRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d",
+                       expectedPresentTime, transaction.originUid);
         return TransactionReadiness::NotReady;
     }
 
     // If the client didn't specify desiredPresentTime, use the vsyncId to determine the
     // expected present time of this transaction.
     if (transaction.isAutoTimestamp && frameIsEarly(expectedPresentTime, vsyncId)) {
-        ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64,
-                      transaction.frameTimelineInfo.vsyncId, expectedPresentTime);
+        SFTRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64,
+                       transaction.frameTimelineInfo.vsyncId, expectedPresentTime);
         return TransactionReadiness::NotReady;
     }
 
@@ -4879,9 +4874,9 @@
                                                  s.bufferData->acquireFence);
                 // Delete the entire state at this point and not just release the buffer because
                 // everything associated with the Layer in this Transaction is now out of date.
-                ATRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d",
-                              layer->getDebugName(), layer->getDrawingState().barrierProducerId,
-                              s.bufferData->producerId);
+                SFTRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d",
+                               layer->getDebugName(), layer->getDrawingState().barrierProducerId,
+                               s.bufferData->producerId);
                 return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
             }
 
@@ -4891,10 +4886,10 @@
                         ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
                           s.bufferData->barrierFrameNumber));
                 if (!willApplyBarrierFrame) {
-                    ATRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64 " > %" PRId64,
-                                  layer->getDebugName(),
-                                  layer->getDrawingState().barrierFrameNumber,
-                                  s.bufferData->barrierFrameNumber);
+                    SFTRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64 " > %" PRId64,
+                                   layer->getDebugName(),
+                                   layer->getDrawingState().barrierFrameNumber,
+                                   s.bufferData->barrierFrameNumber);
                     ready = TransactionReadiness::NotReadyBarrier;
                     return TraverseBuffersReturnValues::STOP_TRAVERSAL;
                 }
@@ -4906,7 +4901,7 @@
         const bool hasPendingBuffer =
                 flushState.bufferLayersReadyToPresent.contains(s.surface.get());
         if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) {
-            ATRACE_FORMAT("hasPendingBuffer %s", layer->getDebugName());
+            SFTRACE_FORMAT("hasPendingBuffer %s", layer->getDebugName());
             ready = TransactionReadiness::NotReady;
             return TraverseBuffersReturnValues::STOP_TRAVERSAL;
         }
@@ -4923,8 +4918,8 @@
                     layer->isSimpleBufferUpdate(s);
 
             if (allowLatchUnsignaled) {
-                ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s",
-                              layer->getDebugName());
+                SFTRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s",
+                               layer->getDebugName());
                 ready = TransactionReadiness::NotReadyUnsignaled;
             } else {
                 ready = TransactionReadiness::NotReady;
@@ -4943,7 +4938,7 @@
                                                         .bufferId = s.bufferData->getId(),
                                                         .frameNumber = s.bufferData->frameNumber});
                 }
-                ATRACE_FORMAT("fence unsignaled %s", layer->getDebugName());
+                SFTRACE_FORMAT("fence unsignaled %s", layer->getDebugName());
                 return TraverseBuffersReturnValues::STOP_TRAVERSAL;
             }
         }
@@ -4973,8 +4968,8 @@
                             uint32_t currentMaxAcquiredBufferCount =
                                     getMaxAcquiredBufferCountForCurrentRefreshRate(
                                             layer->ownerUid.val());
-                            ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64,
-                                                  layer->name.c_str(), s.bufferData->frameNumber);
+                            SFTRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64,
+                                                   layer->name.c_str(), s.bufferData->frameNumber);
                             s.bufferData->releaseBufferListener
                                     ->onReleaseBuffer({resolvedState.externalTexture->getBuffer()
                                                                ->getId(),
@@ -4988,9 +4983,9 @@
                         // Delete the entire state at this point and not just release the buffer
                         // because everything associated with the Layer in this Transaction is now
                         // out of date.
-                        ATRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d",
-                                      layer->name.c_str(), layer->barrierProducerId,
-                                      s.bufferData->producerId);
+                        SFTRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d",
+                                       layer->name.c_str(), layer->barrierProducerId,
+                                       s.bufferData->producerId);
                         return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
                     }
 
@@ -5000,10 +4995,10 @@
                                 ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
                                   s.bufferData->barrierFrameNumber));
                         if (!willApplyBarrierFrame) {
-                            ATRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64
-                                          " > %" PRId64,
-                                          layer->name.c_str(), layer->barrierFrameNumber,
-                                          s.bufferData->barrierFrameNumber);
+                            SFTRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64
+                                           " > %" PRId64,
+                                           layer->name.c_str(), layer->barrierFrameNumber,
+                                           s.bufferData->barrierFrameNumber);
                             ready = TransactionReadiness::NotReadyBarrier;
                             return TraverseBuffersReturnValues::STOP_TRAVERSAL;
                         }
@@ -5016,7 +5011,7 @@
                         flushState.bufferLayersReadyToPresent.contains(s.surface.get());
                 if (layer->backpressureEnabled() && hasPendingBuffer &&
                     transaction.isAutoTimestamp) {
-                    ATRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str());
+                    SFTRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str());
                     ready = TransactionReadiness::NotReady;
                     return TraverseBuffersReturnValues::STOP_TRAVERSAL;
                 }
@@ -5033,8 +5028,8 @@
                                                   flushState.firstTransaction) &&
                             layer->isSimpleBufferUpdate(s);
                     if (allowLatchUnsignaled) {
-                        ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s",
-                                      layer->name.c_str());
+                        SFTRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s",
+                                       layer->name.c_str());
                         ready = TransactionReadiness::NotReadyUnsignaled;
                     } else {
                         ready = TransactionReadiness::NotReady;
@@ -5051,7 +5046,7 @@
                                                                 .frameNumber =
                                                                         s.bufferData->frameNumber});
                         }
-                        ATRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
+                        SFTRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
                         return TraverseBuffersReturnValues::STOP_TRAVERSAL;
                     }
                 }
@@ -5132,22 +5127,22 @@
 bool SurfaceFlinger::shouldLatchUnsignaled(const layer_state_t& state, size_t numStates,
                                            bool firstTransaction) const {
     if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Disabled) {
-        ATRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::Disabled)", __func__);
+        SFTRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::Disabled)", __func__);
         return false;
     }
 
     // We only want to latch unsignaled when a single layer is updated in this
     // transaction (i.e. not a blast sync transaction).
     if (numStates != 1) {
-        ATRACE_FORMAT_INSTANT("%s: false (numStates=%zu)", __func__, numStates);
+        SFTRACE_FORMAT_INSTANT("%s: false (numStates=%zu)", __func__, numStates);
         return false;
     }
 
     if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer) {
         if (!firstTransaction) {
-            ATRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::AutoSingleLayer; not first "
-                                  "transaction)",
-                                  __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::AutoSingleLayer; not first "
+                                   "transaction)",
+                                   __func__);
             return false;
         }
 
@@ -5155,9 +5150,9 @@
         // as it leads to jank due to RenderEngine waiting for unsignaled buffer
         // or window animations being slow.
         if (mScheduler->vsyncModulator().isVsyncConfigEarly()) {
-            ATRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::AutoSingleLayer; "
-                                  "isVsyncConfigEarly)",
-                                  __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::AutoSingleLayer; "
+                                   "isVsyncConfigEarly)",
+                                   __func__);
             return false;
         }
     }
@@ -5167,12 +5162,12 @@
 
 status_t SurfaceFlinger::setTransactionState(
         const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
-        const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+        Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
         InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp,
         const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
         const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
         const std::vector<uint64_t>& mergedTransactionIds) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     IPCThreadState* ipc = IPCThreadState::self();
     const int originPid = ipc->getCallingPid();
@@ -5182,7 +5177,7 @@
         composerState.state.sanitize(permissions);
     }
 
-    for (DisplayState display : displays) {
+    for (DisplayState& display : displays) {
         display.sanitize(permissions);
     }
 
@@ -5505,10 +5500,8 @@
     }
     if (layer == nullptr) {
         for (auto& [listener, callbackIds] : s.listeners) {
-            mTransactionCallbackInvoker.addCallbackHandle(sp<CallbackHandle>::make(listener,
-                                                                                   callbackIds,
-                                                                                   s.surface),
-                                                          std::vector<JankData>());
+            mTransactionCallbackInvoker.addCallbackHandle(
+                    sp<CallbackHandle>::make(listener, callbackIds, s.surface));
         }
         return 0;
     }
@@ -5866,10 +5859,8 @@
     }
     if (layer == nullptr) {
         for (auto& [listener, callbackIds] : s.listeners) {
-            mTransactionCallbackInvoker.addCallbackHandle(sp<CallbackHandle>::make(listener,
-                                                                                   callbackIds,
-                                                                                   s.surface),
-                                                          std::vector<JankData>());
+            mTransactionCallbackInvoker.addCallbackHandle(
+                    sp<CallbackHandle>::make(listener, callbackIds, s.surface));
         }
         return 0;
     }
@@ -6112,6 +6103,8 @@
         mTransactionHandler.onLayerDestroyed(layerId);
     }
 
+    JankTracker::flushJankData(layerId);
+
     Mutex::Autolock lock(mStateLock);
     markLayerPendingRemovalLocked(layer);
     layer->onHandleDestroyed();
@@ -6389,15 +6382,23 @@
         return NO_ERROR;
     }
 
-    // Traversal of drawing state must happen on the main thread.
-    // Otherwise, SortedVector may have shared ownership during concurrent
-    // traversals, which can result in use-after-frees.
+    // Collect debug data from main thread
     std::string compositionLayers;
     mScheduler
             ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
                 dumpVisibleFrontEnd(compositionLayers);
             })
             .get();
+    // get window info listener data without the state lock
+    auto windowInfosDebug = mWindowInfosListenerInvoker->getDebugInfo();
+    compositionLayers.append("Window Infos:\n");
+    StringAppendF(&compositionLayers, "  max send vsync id: %" PRId64 "\n",
+                  ftl::to_underlying(windowInfosDebug.maxSendDelayVsyncId));
+    StringAppendF(&compositionLayers, "  max send delay (ns): %" PRId64 " ns\n",
+                  windowInfosDebug.maxSendDelayDuration);
+    StringAppendF(&compositionLayers, "  unsent messages: %zu\n",
+                  windowInfosDebug.pendingMessageCount);
+    compositionLayers.append("\n");
     dumpAll(args, compositionLayers, result);
     write(fd, result.c_str(), result.size());
     return NO_ERROR;
@@ -6449,7 +6450,7 @@
     if (now - sTimestamp < 30min) return;
     sTimestamp = now;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mDrawingState.traverse([&](Layer* layer) { layer->logFrameStats(); });
 }
 
@@ -6980,15 +6981,6 @@
 
     result.append(mTimeStats->miniDump());
     result.append("\n");
-
-    result.append("Window Infos:\n");
-    auto windowInfosDebug = mWindowInfosListenerInvoker->getDebugInfo();
-    StringAppendF(&result, "  max send vsync id: %" PRId64 "\n",
-                  ftl::to_underlying(windowInfosDebug.maxSendDelayVsyncId));
-    StringAppendF(&result, "  max send delay (ns): %" PRId64 " ns\n",
-                  windowInfosDebug.maxSendDelayDuration);
-    StringAppendF(&result, "  unsent messages: %zu\n", windowInfosDebug.pendingMessageCount);
-    result.append("\n");
 }
 
 mat4 SurfaceFlinger::calculateColorMatrix(float saturation) {
@@ -7763,7 +7755,7 @@
     switch (action) {
         case KernelIdleTimerAction::TurnOff:
             if (mKernelIdleTimerEnabled) {
-                ATRACE_INT("KernelIdleTimer", 0);
+                SFTRACE_INT("KernelIdleTimer", 0);
                 std::chrono::milliseconds constexpr kTimerDisabledTimeout = 0ms;
                 updateKernelIdleTimer(kTimerDisabledTimeout, kernelIdleTimerController.value(),
                                       display->getPhysicalId());
@@ -7772,7 +7764,7 @@
             break;
         case KernelIdleTimerAction::TurnOn:
             if (!mKernelIdleTimerEnabled) {
-                ATRACE_INT("KernelIdleTimer", 1);
+                SFTRACE_INT("KernelIdleTimer", 1);
                 const std::chrono::milliseconds timeout =
                         display->refreshRateSelector().getIdleTimerTimeout();
                 updateKernelIdleTimer(timeout, kernelIdleTimerController.value(),
@@ -7901,7 +7893,7 @@
 
 void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args,
                                     const sp<IScreenCaptureListener>& captureListener) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     status_t validate = validateScreenshotPermissions(args);
     if (validate != OK) {
@@ -7957,10 +7949,13 @@
     GetLayerSnapshotsFunction getLayerSnapshotsFn =
             getLayerSnapshotsForScreenshots(layerStack, args.uid, std::move(excludeLayerIds));
 
+    ftl::Flags<RenderArea::Options> options;
+    if (args.captureSecureLayers) options |= RenderArea::Options::CAPTURE_SECURE_LAYERS;
+    if (args.hintForSeamlessTransition)
+        options |= RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION;
     captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type<DisplayRenderAreaBuilder>,
                                                  args.sourceCrop, reqSize, args.dataspace,
-                                                 args.hintForSeamlessTransition,
-                                                 args.captureSecureLayers, displayWeak),
+                                                 displayWeak, options),
                         getLayerSnapshotsFn, reqSize, args.pixelFormat, args.allowProtected,
                         args.grayscale, captureListener);
 }
@@ -8011,10 +8006,12 @@
     constexpr bool kAllowProtected = false;
     constexpr bool kGrayscale = false;
 
+    ftl::Flags<RenderArea::Options> options;
+    if (args.hintForSeamlessTransition)
+        options |= RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION;
     captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type<DisplayRenderAreaBuilder>,
-                                                 Rect(), size, args.dataspace,
-                                                 args.hintForSeamlessTransition,
-                                                 false /* captureSecureLayers */, displayWeak),
+                                                 Rect(), size, args.dataspace, displayWeak,
+                                                 options),
                         getLayerSnapshotsFn, size, args.pixelFormat, kAllowProtected, kGrayscale,
                         captureListener);
 }
@@ -8027,7 +8024,7 @@
 
 void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
                                    const sp<IScreenCaptureListener>& captureListener) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     status_t validate = validateScreenshotPermissions(args);
     if (validate != OK) {
@@ -8113,10 +8110,13 @@
         return;
     }
 
+    ftl::Flags<RenderArea::Options> options;
+    if (args.captureSecureLayers) options |= RenderArea::Options::CAPTURE_SECURE_LAYERS;
+    if (args.hintForSeamlessTransition)
+        options |= RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION;
     captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type<LayerRenderAreaBuilder>, crop,
-                                                 reqSize, dataspace, args.captureSecureLayers,
-                                                 args.hintForSeamlessTransition, parent,
-                                                 args.childrenOnly),
+                                                 reqSize, dataspace, parent, args.childrenOnly,
+                                                 options),
                         getLayerSnapshotsFn, reqSize, args.pixelFormat, args.allowProtected,
                         args.grayscale, captureListener);
 }
@@ -8152,12 +8152,12 @@
 // Accessing display requires mStateLock, and contention for this lock
 // is reduced when grabbed from the main thread, thus also reducing
 // risk of deadlocks.
-std::optional<SurfaceFlinger::OutputCompositionState>
-SurfaceFlinger::getDisplayAndLayerSnapshotsFromMainThread(
+std::optional<SurfaceFlinger::OutputCompositionState> SurfaceFlinger::getSnapshotsFromMainThread(
         RenderAreaBuilderVariant& renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn,
         std::vector<sp<LayerFE>>& layerFEs) {
     return mScheduler
             ->schedule([=, this, &renderAreaBuilder, &layerFEs]() REQUIRES(kMainThreadContext) {
+                SFTRACE_NAME("getSnapshotsFromMainThread");
                 auto layers = getLayerSnapshotsFn();
                 for (auto& [layer, layerFE] : layers) {
                     attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK);
@@ -8173,7 +8173,7 @@
                                          ui::Size bufferSize, ui::PixelFormat reqPixelFormat,
                                          bool allowProtected, bool grayscale,
                                          const sp<IScreenCaptureListener>& captureListener) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (exceedsMaxRenderTargetSize(bufferSize.getWidth(), bufferSize.getHeight())) {
         ALOGE("Attempted to capture screen with size (%" PRId32 ", %" PRId32
@@ -8184,11 +8184,10 @@
     }
 
     if (FlagManager::getInstance().single_hop_screenshot() &&
-        FlagManager::getInstance().ce_fence_promise()) {
+        FlagManager::getInstance().ce_fence_promise() && mRenderEngine->isThreaded()) {
         std::vector<sp<LayerFE>> layerFEs;
         auto displayState =
-                getDisplayAndLayerSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn,
-                                                          layerFEs);
+                getSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn, layerFEs);
 
         const bool supportsProtected = getRenderEngine().supportsProtectedContent();
         bool hasProtectedLayer = false;
@@ -8311,7 +8310,7 @@
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
         bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener,
         std::optional<OutputCompositionState>& displayState, std::vector<sp<LayerFE>>& layerFEs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     ScreenCaptureResults captureResults;
     std::unique_ptr<const RenderArea> renderArea =
@@ -8351,7 +8350,7 @@
         RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
         bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES(
                                     kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
@@ -8415,7 +8414,7 @@
         bool grayscale, bool isProtected, ScreenCaptureResults& captureResults,
         std::optional<OutputCompositionState>& displayState,
         std::vector<std::pair<Layer*, sp<LayerFE>>>& layers, std::vector<sp<LayerFE>>& layerFEs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     for (auto& layerFE : layerFEs) {
         frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get();
@@ -8558,10 +8557,8 @@
     // to CompositionEngine::present.
     ftl::SharedFuture<FenceResult> presentFuture;
     if (FlagManager::getInstance().single_hop_screenshot() &&
-        FlagManager::getInstance().ce_fence_promise()) {
-        presentFuture = mRenderEngine->isThreaded()
-                ? ftl::yield(present()).share()
-                : mScheduler->schedule(std::move(present)).share();
+        FlagManager::getInstance().ce_fence_promise() && mRenderEngine->isThreaded()) {
+        presentFuture = ftl::yield(present()).share();
     } else {
         presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share()
                                                     : ftl::yield(present()).share();
@@ -8673,7 +8670,7 @@
         const sp<DisplayDevice>& display,
         const scheduler::RefreshRateSelector::PolicyVariant& policy) {
     const auto displayId = display->getPhysicalId();
-    ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
     Mutex::Autolock lock(mStateLock);
 
@@ -8766,7 +8763,7 @@
 
 status_t SurfaceFlinger::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
                                                     const gui::DisplayModeSpecs& specs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!displayToken) {
         return BAD_VALUE;
@@ -8800,7 +8797,7 @@
 
 status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
                                                     gui::DisplayModeSpecs* outSpecs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!displayToken || !outSpecs) {
         return BAD_VALUE;
@@ -9092,7 +9089,7 @@
 
 void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveDisplayPtr,
                                                   const DisplayDevice& activeDisplay) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (inactiveDisplayPtr) {
         inactiveDisplayPtr->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false);
@@ -9335,7 +9332,9 @@
     std::vector<std::pair<Layer*, LayerFE*>> layers;
     if (mLayerLifecycleManagerEnabled) {
         nsecs_t currentTime = systemTime();
-        mLayerSnapshotBuilder.forEachVisibleSnapshot(
+        const bool needsMetadata = mCompositionEngine->getFeatureFlags().test(
+                compositionengine::Feature::kSnapshotLayerMetadata);
+        mLayerSnapshotBuilder.forEachSnapshot(
                 [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) FTL_FAKE_GUARD(
                         kMainThreadContext) {
                     if (cursorOnly &&
@@ -9358,6 +9357,12 @@
                     layerFE->mSnapshot = std::move(snapshot);
                     refreshArgs.layers.push_back(layerFE);
                     layers.emplace_back(legacyLayer.get(), layerFE.get());
+                },
+                [needsMetadata](const frontend::LayerSnapshot& snapshot) {
+                    return snapshot.isVisible ||
+                            (needsMetadata &&
+                             snapshot.changes.test(
+                                     frontend::RequestedLayerState::Changes::Metadata));
                 });
     }
     if (!mLayerLifecycleManagerEnabled) {
@@ -9518,7 +9523,7 @@
 
 frontend::Update SurfaceFlinger::flushLifecycleUpdates() {
     frontend::Update update;
-    ATRACE_NAME("TransactionHandler:flushTransactions");
+    SFTRACE_NAME("TransactionHandler:flushTransactions");
     // Locking:
     // 1. to prevent onHandleDestroyed from being called while the state lock is held,
     // we must keep a copy of the transactions (specifically the composer
@@ -9564,7 +9569,7 @@
 
 perfetto::protos::LayersSnapshotProto SurfaceFlinger::takeLayersSnapshotProto(
         uint32_t traceFlags, TimePoint time, VsyncId vsyncId, bool visibleRegionDirty) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     perfetto::protos::LayersSnapshotProto snapshot;
     snapshot.set_elapsed_realtime_nanos(time.ns());
     snapshot.set_vsync_id(ftl::to_underlying(vsyncId));
@@ -10454,6 +10459,28 @@
     return ::android::binder::Status::ok();
 }
 
+binder::Status SurfaceComposerAIDL::addJankListener(const sp<IBinder>& layerHandle,
+                                                    const sp<gui::IJankListener>& listener) {
+    sp<Layer> layer = LayerHandle::getLayer(layerHandle);
+    if (layer == nullptr) {
+        return binder::Status::fromExceptionCode(binder::Status::EX_NULL_POINTER);
+    }
+    JankTracker::addJankListener(layer->sequence, IInterface::asBinder(listener));
+    return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::flushJankData(int32_t layerId) {
+    JankTracker::flushJankData(layerId);
+    return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::removeJankListener(int32_t layerId,
+                                                       const sp<gui::IJankListener>& listener,
+                                                       int64_t afterVsync) {
+    JankTracker::removeJankListener(layerId, IInterface::asBinder(listener), afterVsync);
+    return binder::Status::ok();
+}
+
 status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) {
     if (!mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(usePermissionCache)) {
         IPCThreadState* ipc = IPCThreadState::self();
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index ee541c4..8242844 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -27,7 +27,9 @@
 #include <android/gui/BnSurfaceComposer.h>
 #include <android/gui/DisplayStatInfo.h>
 #include <android/gui/DisplayState.h>
+#include <android/gui/IJankListener.h>
 #include <android/gui/ISurfaceComposerClient.h>
+#include <common/trace.h>
 #include <cutils/atomic.h>
 #include <cutils/compiler.h>
 #include <ftl/algorithm.h>
@@ -52,7 +54,6 @@
 #include <utils/KeyedVector.h>
 #include <utils/RefBase.h>
 #include <utils/SortedVector.h>
-#include <utils/Trace.h>
 #include <utils/threads.h>
 
 #include <compositionengine/OutputColorSetting.h>
@@ -449,7 +450,7 @@
             if (it != mCounterByLayerHandle.end()) {
                 auto [name, pendingBuffers] = it->second;
                 int32_t count = ++(*pendingBuffers);
-                ATRACE_INT(name.c_str(), count);
+                SFTRACE_INT(name.c_str(), count);
             } else {
                 ALOGW("Handle not found! %p", layerHandle);
             }
@@ -559,7 +560,7 @@
     sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const;
     status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
             bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
@@ -896,7 +897,7 @@
 
     using OutputCompositionState = compositionengine::impl::OutputCompositionState;
 
-    std::optional<OutputCompositionState> getDisplayAndLayerSnapshotsFromMainThread(
+    std::optional<OutputCompositionState> getSnapshotsFromMainThread(
             RenderAreaBuilderVariant& renderAreaBuilder,
             GetLayerSnapshotsFunction getLayerSnapshotsFn, std::vector<sp<LayerFE>>& layerFEs);
 
@@ -1693,6 +1694,11 @@
             int pid, std::optional<gui::StalledTransactionInfo>* outInfo) override;
     binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) override;
     binder::Status notifyShutdown() override;
+    binder::Status addJankListener(const sp<IBinder>& layer,
+                                   const sp<gui::IJankListener>& listener) override;
+    binder::Status flushJankData(int32_t layerId) override;
+    binder::Status removeJankListener(int32_t layerId, const sp<gui::IJankListener>& listener,
+                                      int64_t afterVsync) override;
 
 private:
     static const constexpr bool kUsePermissionCache = true;
diff --git a/services/surfaceflinger/TimeStats/Android.bp b/services/surfaceflinger/TimeStats/Android.bp
index a631074..ea86911 100644
--- a/services/surfaceflinger/TimeStats/Android.bp
+++ b/services/surfaceflinger/TimeStats/Android.bp
@@ -24,6 +24,7 @@
 
     static_libs: [
         "libtimestats_proto",
+        "libsurfaceflinger_common",
     ],
 
     export_static_lib_headers: [
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 368cb41..c60ded6 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -19,11 +19,11 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <log/log.h>
 #include <timestatsatomsproto/TimeStatsAtomsProtoHeader.h>
 #include <utils/String8.h>
 #include <utils/Timers.h>
-#include <utils/Trace.h>
 
 #include <algorithm>
 #include <chrono>
@@ -271,7 +271,7 @@
 }
 
 void TimeStats::parseArgs(bool asProto, const Vector<String16>& args, std::string& result) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::unordered_map<std::string, int32_t> argsMap;
     for (size_t index = 0; index < args.size(); ++index) {
@@ -304,7 +304,7 @@
 }
 
 std::string TimeStats::miniDump() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::string result = "TimeStats miniDump:\n";
     std::lock_guard<std::mutex> lock(mMutex);
@@ -318,7 +318,7 @@
 void TimeStats::incrementTotalFrames() {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     mTimeStats.totalFramesLegacy++;
@@ -327,7 +327,7 @@
 void TimeStats::incrementMissedFrames() {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     mTimeStats.missedFramesLegacy++;
@@ -338,7 +338,7 @@
         return;
     }
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     if (record.changed) mTimeStats.compositionStrategyChangesLegacy++;
@@ -351,7 +351,7 @@
 void TimeStats::incrementRefreshRateSwitches() {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     mTimeStats.refreshRateSwitchesLegacy++;
@@ -445,7 +445,7 @@
                                                    std::optional<Fps> renderRate,
                                                    SetFrameRateVote frameRateVote,
                                                    GameMode gameMode) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-flushAvailableRecordsToStatsLocked", layerId);
 
     LayerRecord& layerRecord = mTimeStatsTracker[layerId];
@@ -568,7 +568,7 @@
                             uid_t uid, nsecs_t postTime, GameMode gameMode) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-[%s]-PostTime[%" PRId64 "]", layerId, frameNumber, layerName.c_str(),
           postTime);
 
@@ -612,7 +612,7 @@
 void TimeStats::setLatchTime(int32_t layerId, uint64_t frameNumber, nsecs_t latchTime) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-LatchTime[%" PRId64 "]", layerId, frameNumber, latchTime);
 
     std::lock_guard<std::mutex> lock(mMutex);
@@ -630,7 +630,7 @@
 void TimeStats::incrementLatchSkipped(int32_t layerId, LatchSkipReason reason) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-LatchSkipped-Reason[%d]", layerId,
           static_cast<std::underlying_type<LatchSkipReason>::type>(reason));
 
@@ -648,7 +648,7 @@
 void TimeStats::incrementBadDesiredPresent(int32_t layerId) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-BadDesiredPresent", layerId);
 
     std::lock_guard<std::mutex> lock(mMutex);
@@ -660,7 +660,7 @@
 void TimeStats::setDesiredTime(int32_t layerId, uint64_t frameNumber, nsecs_t desiredTime) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-DesiredTime[%" PRId64 "]", layerId, frameNumber, desiredTime);
 
     std::lock_guard<std::mutex> lock(mMutex);
@@ -678,7 +678,7 @@
 void TimeStats::setAcquireTime(int32_t layerId, uint64_t frameNumber, nsecs_t acquireTime) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-AcquireTime[%" PRId64 "]", layerId, frameNumber, acquireTime);
 
     std::lock_guard<std::mutex> lock(mMutex);
@@ -697,7 +697,7 @@
                                 const std::shared_ptr<FenceTime>& acquireFence) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-AcquireFenceTime[%" PRId64 "]", layerId, frameNumber,
           acquireFence->getSignalTime());
 
@@ -718,7 +718,7 @@
                                SetFrameRateVote frameRateVote, GameMode gameMode) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-PresentTime[%" PRId64 "]", layerId, frameNumber, presentTime);
 
     std::lock_guard<std::mutex> lock(mMutex);
@@ -744,7 +744,7 @@
                                 SetFrameRateVote frameRateVote, GameMode gameMode) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-PresentFenceTime[%" PRId64 "]", layerId, frameNumber,
           presentFence->getSignalTime());
 
@@ -805,7 +805,7 @@
 void TimeStats::incrementJankyFrames(const JankyFramesInfo& info) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard<std::mutex> lock(mMutex);
 
     // Only update layer stats if we're already tracking the layer in TimeStats.
@@ -861,7 +861,7 @@
 }
 
 void TimeStats::onDestroy(int32_t layerId) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-onDestroy", layerId);
     std::lock_guard<std::mutex> lock(mMutex);
     mTimeStatsTracker.erase(layerId);
@@ -870,7 +870,7 @@
 void TimeStats::removeTimeRecord(int32_t layerId, uint64_t frameNumber) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-removeTimeRecord", layerId, frameNumber);
 
     std::lock_guard<std::mutex> lock(mMutex);
@@ -935,7 +935,7 @@
 }
 
 void TimeStats::flushAvailableGlobalRecordsToStatsLocked() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     while (!mGlobalRecord.presentFences.empty()) {
         const nsecs_t curPresentTime = mGlobalRecord.presentFences.front()->getSignalTime();
@@ -992,7 +992,7 @@
 void TimeStats::setPresentFenceGlobal(const std::shared_ptr<FenceTime>& presentFence) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard<std::mutex> lock(mMutex);
     if (presentFence == nullptr || !presentFence->isValid()) {
         mGlobalRecord.prevPresentTime = 0;
@@ -1022,7 +1022,7 @@
 void TimeStats::enable() {
     if (mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     mEnabled.store(true);
@@ -1034,7 +1034,7 @@
 void TimeStats::disable() {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     flushPowerTimeLocked();
@@ -1051,7 +1051,7 @@
 }
 
 void TimeStats::clearGlobalLocked() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     mTimeStats.statsStartLegacy = (mEnabled.load() ? static_cast<int64_t>(std::time(0)) : 0);
     mTimeStats.statsEndLegacy = 0;
@@ -1078,7 +1078,7 @@
 }
 
 void TimeStats::clearLayersLocked() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     mTimeStatsTracker.clear();
 
@@ -1093,7 +1093,7 @@
 }
 
 void TimeStats::dump(bool asProto, std::optional<uint32_t> maxLayers, std::string& result) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     if (mTimeStats.statsStartLegacy == 0) {
diff --git a/services/surfaceflinger/TracedOrdinal.h b/services/surfaceflinger/TracedOrdinal.h
index 1adc3a5..eba1ecf 100644
--- a/services/surfaceflinger/TracedOrdinal.h
+++ b/services/surfaceflinger/TracedOrdinal.h
@@ -21,8 +21,8 @@
 #include <functional>
 #include <string>
 
+#include <common/trace.h>
 #include <cutils/compiler.h>
-#include <utils/Trace.h>
 
 namespace android {
 
@@ -88,13 +88,13 @@
         }
 
         if (!signbit(mData)) {
-            ATRACE_INT64(mName.c_str(), to_int64(mData));
+            SFTRACE_INT64(mName.c_str(), to_int64(mData));
             if (mHasGoneNegative) {
-                ATRACE_INT64(mNameNegative.c_str(), 0);
+                SFTRACE_INT64(mNameNegative.c_str(), 0);
             }
         } else {
-            ATRACE_INT64(mNameNegative.c_str(), -to_int64(mData));
-            ATRACE_INT64(mName.c_str(), 0);
+            SFTRACE_INT64(mNameNegative.c_str(), -to_int64(mData));
+            SFTRACE_INT64(mName.c_str(), 0);
         }
     }
 
diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp
index 41bcdf0..d40b888 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.cpp
+++ b/services/surfaceflinger/Tracing/LayerTracing.cpp
@@ -24,10 +24,10 @@
 #include "Tracing/tools/LayerTraceGenerator.h"
 #include "TransactionTracing.h"
 
+#include <common/trace.h>
 #include <log/log.h>
 #include <perfetto/tracing.h>
 #include <utils/Timers.h>
-#include <utils/Trace.h>
 
 namespace android {
 
@@ -134,7 +134,7 @@
 
 void LayerTracing::addProtoSnapshotToOstream(perfetto::protos::LayersSnapshotProto&& snapshot,
                                              Mode mode) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mOutStream) {
         writeSnapshotToStream(std::move(snapshot));
     } else {
diff --git a/services/surfaceflinger/Tracing/TransactionRingBuffer.h b/services/surfaceflinger/Tracing/TransactionRingBuffer.h
index 7d1d3fd..2b66391 100644
--- a/services/surfaceflinger/Tracing/TransactionRingBuffer.h
+++ b/services/surfaceflinger/Tracing/TransactionRingBuffer.h
@@ -19,13 +19,12 @@
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
 
+#include <common/trace.h>
 #include <log/log.h>
 #include <utils/Errors.h>
 #include <utils/Timers.h>
-#include <utils/Trace.h>
 #include <chrono>
 #include <fstream>
-#include <queue>
 
 namespace android {
 
@@ -57,7 +56,7 @@
     }
 
     status_t appendToStream(FileProto& fileProto, std::ofstream& out) {
-        ATRACE_CALL();
+        SFTRACE_CALL();
         writeToProto(fileProto);
         std::string output;
         if (!fileProto.SerializeToString(&output)) {
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index 222ae30..881bf35 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -27,10 +27,9 @@
 #include "BackgroundExecutor.h"
 #include "Utils/FenceUtils.h"
 
-#include <cinttypes>
-
 #include <binder/IInterface.h>
 #include <common/FlagManager.h>
+#include <common/trace.h>
 #include <utils/RefBase.h>
 
 namespace android {
@@ -64,13 +63,12 @@
     if (handles.empty()) {
         return NO_ERROR;
     }
-    const std::vector<JankData>& jankData = std::vector<JankData>();
     for (const auto& handle : handles) {
         if (!containsOnCommitCallbacks(handle->callbackIds)) {
             outRemainingHandles.push_back(handle);
             continue;
         }
-        status_t err = addCallbackHandle(handle, jankData);
+        status_t err = addCallbackHandle(handle);
         if (err != NO_ERROR) {
             return err;
         }
@@ -80,12 +78,12 @@
 }
 
 status_t TransactionCallbackInvoker::addCallbackHandles(
-        const std::deque<sp<CallbackHandle>>& handles, const std::vector<JankData>& jankData) {
+        const std::deque<sp<CallbackHandle>>& handles) {
     if (handles.empty()) {
         return NO_ERROR;
     }
     for (const auto& handle : handles) {
-        status_t err = addCallbackHandle(handle, jankData);
+        status_t err = addCallbackHandle(handle);
         if (err != NO_ERROR) {
             return err;
         }
@@ -111,8 +109,7 @@
     return NO_ERROR;
 }
 
-status_t TransactionCallbackInvoker::addCallbackHandle(const sp<CallbackHandle>& handle,
-        const std::vector<JankData>& jankData) {
+status_t TransactionCallbackInvoker::addCallbackHandle(const sp<CallbackHandle>& handle) {
     // If we can't find the transaction stats something has gone wrong. The client should call
     // startRegistration before trying to add a callback handle.
     TransactionStats* transactionStats;
@@ -151,8 +148,7 @@
                                                     handle->previousReleaseFence,
                                                     handle->transformHint,
                                                     handle->currentMaxAcquiredBufferCount,
-                                                    eventStats, jankData,
-                                                    handle->previousReleaseCallbackId);
+                                                    eventStats, handle->previousReleaseCallbackId);
     }
     return NO_ERROR;
 }
@@ -164,7 +160,7 @@
 void TransactionCallbackInvoker::sendCallbacks(bool onCommitOnly) {
     // For each listener
     auto completedTransactionsItr = mCompletedTransactions.begin();
-    BackgroundExecutor::Callbacks callbacks;
+    ftl::SmallVector<ListenerStats, 10> listenerStatsToSend;
     while (completedTransactionsItr != mCompletedTransactions.end()) {
         auto& [listener, transactionStatsDeque] = *completedTransactionsItr;
         ListenerStats listenerStats;
@@ -199,10 +195,7 @@
                 // keep it as an IBinder due to consistency reasons: if we
                 // interface_cast at the IPC boundary when reading a Parcel,
                 // we get pointers that compare unequal in the SF process.
-                callbacks.emplace_back([stats = std::move(listenerStats)]() {
-                    interface_cast<ITransactionCompletedListener>(stats.listener)
-                            ->onTransactionCompleted(stats);
-                });
+                listenerStatsToSend.emplace_back(std::move(listenerStats));
             }
         }
         completedTransactionsItr++;
@@ -212,7 +205,14 @@
         mPresentFence.clear();
     }
 
-    BackgroundExecutor::getInstance().sendCallbacks(std::move(callbacks));
+    BackgroundExecutor::getInstance().sendCallbacks(
+            {[listenerStatsToSend = std::move(listenerStatsToSend)]() {
+                SFTRACE_NAME("TransactionCallbackInvoker::sendCallbacks");
+                for (auto& stats : listenerStatsToSend) {
+                    interface_cast<ITransactionCompletedListener>(stats.listener)
+                            ->onTransactionCompleted(stats);
+                }
+            }});
 }
 
 // -----------------------------------------------------------------------
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index cb7150b..7853a9f 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -63,8 +63,7 @@
 
 class TransactionCallbackInvoker {
 public:
-    status_t addCallbackHandles(const std::deque<sp<CallbackHandle>>& handles,
-                                const std::vector<JankData>& jankData);
+    status_t addCallbackHandles(const std::deque<sp<CallbackHandle>>& handles);
     status_t addOnCommitCallbackHandles(const std::deque<sp<CallbackHandle>>& handles,
                                              std::deque<sp<CallbackHandle>>& outRemainingHandles);
 
@@ -77,9 +76,7 @@
         mCompletedTransactions.clear();
     }
 
-    status_t addCallbackHandle(const sp<CallbackHandle>& handle,
-                               const std::vector<JankData>& jankData);
-
+    status_t addCallbackHandle(const sp<CallbackHandle>& handle);
 
 private:
     status_t findOrCreateTransactionStats(const sp<IBinder>& listener,
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index effbfdb..895e054 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -17,8 +17,8 @@
 #include <android/gui/BnWindowInfosPublisher.h>
 #include <android/gui/IWindowInfosPublisher.h>
 #include <android/gui/WindowInfosListenerInfo.h>
+#include <common/trace.h>
 #include <gui/ISurfaceComposer.h>
-#include <gui/TraceUtils.h>
 #include <gui/WindowInfosUpdate.h>
 #include <scheduler/Time.h>
 
@@ -42,7 +42,7 @@
 
     BackgroundExecutor::getInstance().sendCallbacks(
             {[this, listener = std::move(listener), listenerId]() {
-                ATRACE_NAME("WindowInfosListenerInvoker::addWindowInfosListener");
+                SFTRACE_NAME("WindowInfosListenerInvoker::addWindowInfosListener");
                 sp<IBinder> asBinder = IInterface::asBinder(listener);
                 asBinder->linkToDeath(sp<DeathRecipient>::fromExisting(this));
                 mWindowInfosListeners.try_emplace(asBinder,
@@ -53,7 +53,7 @@
 void WindowInfosListenerInvoker::removeWindowInfosListener(
         const sp<IWindowInfosListener>& listener) {
     BackgroundExecutor::getInstance().sendCallbacks({[this, listener]() {
-        ATRACE_NAME("WindowInfosListenerInvoker::removeWindowInfosListener");
+        SFTRACE_NAME("WindowInfosListenerInvoker::removeWindowInfosListener");
         sp<IBinder> asBinder = IInterface::asBinder(listener);
         asBinder->unlinkToDeath(sp<DeathRecipient>::fromExisting(this));
         eraseListenerAndAckMessages(asBinder);
@@ -62,7 +62,7 @@
 
 void WindowInfosListenerInvoker::binderDied(const wp<IBinder>& who) {
     BackgroundExecutor::getInstance().sendCallbacks({[this, who]() {
-        ATRACE_NAME("WindowInfosListenerInvoker::binderDied");
+        SFTRACE_NAME("WindowInfosListenerInvoker::binderDied");
         eraseListenerAndAckMessages(who);
     }});
 }
@@ -146,7 +146,7 @@
 WindowInfosListenerInvoker::DebugInfo WindowInfosListenerInvoker::getDebugInfo() {
     DebugInfo result;
     BackgroundExecutor::getInstance().sendCallbacks({[&, this]() {
-        ATRACE_NAME("WindowInfosListenerInvoker::getDebugInfo");
+        SFTRACE_NAME("WindowInfosListenerInvoker::getDebugInfo");
         updateMaxSendDelay();
         result = mDebugInfo;
         result.pendingMessageCount = mUnackedState.size();
@@ -169,7 +169,7 @@
 binder::Status WindowInfosListenerInvoker::ackWindowInfosReceived(int64_t vsyncId,
                                                                   int64_t listenerId) {
     BackgroundExecutor::getInstance().sendCallbacks({[this, vsyncId, listenerId]() {
-        ATRACE_NAME("WindowInfosListenerInvoker::ackWindowInfosReceived");
+        SFTRACE_NAME("WindowInfosListenerInvoker::ackWindowInfosReceived");
         auto it = mUnackedState.find(vsyncId);
         if (it == mUnackedState.end()) {
             return;
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 2e3273c..a56bb51 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -136,6 +136,7 @@
     DUMP_READ_ONLY_FLAG(vulkan_renderengine);
     DUMP_READ_ONLY_FLAG(renderable_buffer_usage);
     DUMP_READ_ONLY_FLAG(vrr_bugfix_24q4);
+    DUMP_READ_ONLY_FLAG(vrr_bugfix_dropped_frame);
     DUMP_READ_ONLY_FLAG(restore_blur_step);
     DUMP_READ_ONLY_FLAG(dont_skip_on_early_ro);
     DUMP_READ_ONLY_FLAG(protected_if_client);
@@ -242,6 +243,7 @@
 FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "")
 FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "")
 FLAG_MANAGER_READ_ONLY_FLAG(vrr_bugfix_24q4, "");
+FLAG_MANAGER_READ_ONLY_FLAG(vrr_bugfix_dropped_frame, "")
 FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, "");
 FLAG_MANAGER_READ_ONLY_FLAG(graphite_renderengine, "debug.renderengine.graphite")
 FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, "");
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index ab7a474..8799295 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -73,6 +73,7 @@
     bool screenshot_fence_preservation() const;
     bool vulkan_renderengine() const;
     bool vrr_bugfix_24q4() const;
+    bool vrr_bugfix_dropped_frame() const;
     bool renderable_buffer_usage() const;
     bool restore_blur_step() const;
     bool dont_skip_on_early_ro() const;
diff --git a/services/surfaceflinger/common/include/common/trace.h b/services/surfaceflinger/common/include/common/trace.h
new file mode 100644
index 0000000..c13cdde
--- /dev/null
+++ b/services/surfaceflinger/common/include/common/trace.h
@@ -0,0 +1,104 @@
+
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cutils/trace.h>
+#include <stdint.h>
+
+#define SFTRACE_ENABLED() ATRACE_ENABLED()
+#define SFTRACE_BEGIN(name) ATRACE_BEGIN(name)
+#define SFTRACE_END() ATRACE_END()
+#define SFTRACE_ASYNC_BEGIN(name, cookie) ATRACE_ASYNC_BEGIN(name, cookie)
+#define SFTRACE_ASYNC_END(name, cookie) ATRACE_ASYNC_END(name, cookie)
+#define SFTRACE_ASYNC_FOR_TRACK_BEGIN(track_name, name, cookie) \
+    ATRACE_ASYNC_FOR_TRACK_BEGIN(track_name, name, cookie)
+#define SFTRACE_ASYNC_FOR_TRACK_END(track_name, cookie) \
+    ATRACE_ASYNC_FOR_TRACK_END(track_name, cookie)
+#define SFTRACE_INSTANT(name) ATRACE_INSTANT(name)
+#define SFTRACE_INSTANT_FOR_TRACK(trackName, name) ATRACE_INSTANT_FOR_TRACK(trackName, name)
+#define SFTRACE_INT(name, value) ATRACE_INT(name, value)
+#define SFTRACE_INT64(name, value) ATRACE_INT64(name, value)
+
+// SFTRACE_NAME traces from its location until the end of its enclosing scope.
+#define _PASTE(x, y) x##y
+#define PASTE(x, y) _PASTE(x, y)
+#define SFTRACE_NAME(name) ::android::ScopedTrace PASTE(___tracer, __LINE__)(ATRACE_TAG, name)
+
+// SFTRACE_CALL is an ATRACE_NAME that uses the current function name.
+#define SFTRACE_CALL() SFTRACE_NAME(__FUNCTION__)
+
+#define SFTRACE_FORMAT(fmt, ...)                                                \
+    TraceUtils::TraceEnder traceEnder =                                         \
+            (CC_UNLIKELY(ATRACE_ENABLED()) &&                                   \
+                     (TraceUtils::atraceFormatBegin(fmt, ##__VA_ARGS__), true), \
+             TraceUtils::TraceEnder())
+
+#define SFTRACE_FORMAT_INSTANT(fmt, ...) \
+    (CC_UNLIKELY(ATRACE_ENABLED()) && (TraceUtils::instantFormat(fmt, ##__VA_ARGS__), true))
+
+#define ALOGE_AND_TRACE(fmt, ...)                   \
+    do {                                            \
+        ALOGE(fmt, ##__VA_ARGS__);                  \
+        SFTRACE_FORMAT_INSTANT(fmt, ##__VA_ARGS__); \
+    } while (false)
+
+namespace android {
+
+class TraceUtils {
+public:
+    class TraceEnder {
+    public:
+        ~TraceEnder() { ATRACE_END(); }
+    };
+
+    static void atraceFormatBegin(const char* fmt, ...) {
+        const int BUFFER_SIZE = 256;
+        va_list ap;
+        char buf[BUFFER_SIZE];
+
+        va_start(ap, fmt);
+        vsnprintf(buf, BUFFER_SIZE, fmt, ap);
+        va_end(ap);
+
+        SFTRACE_BEGIN(buf);
+    }
+
+    static void instantFormat(const char* fmt, ...) {
+        const int BUFFER_SIZE = 256;
+        va_list ap;
+        char buf[BUFFER_SIZE];
+
+        va_start(ap, fmt);
+        vsnprintf(buf, BUFFER_SIZE, fmt, ap);
+        va_end(ap);
+
+        SFTRACE_INSTANT(buf);
+    }
+};
+
+class ScopedTrace {
+public:
+    inline ScopedTrace(uint64_t tag, const char* name) : mTag(tag) { atrace_begin(mTag, name); }
+
+    inline ~ScopedTrace() { atrace_end(mTag); }
+
+private:
+    uint64_t mTag;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index f4d4ee9..919ec17 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -136,4 +136,15 @@
   }
 } # vrr_bugfix_24q4
 
+flag {
+  name: "vrr_bugfix_dropped_frame"
+  namespace: "core_graphics"
+  description: "bug fix for VRR dropped frame"
+  bug: "343603085"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # vrr_bugfix_dropped_frame
+
 # IMPORTANT - please keep alphabetize to reduce merge conflicts
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index ebe11fb..d355e72 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -26,6 +26,7 @@
 #include <private/android_filesystem_config.h>
 #include <private/gui/ComposerServiceAIDL.h>
 #include <ui/DisplayMode.h>
+#include <ui/DisplayState.h>
 #include <ui/DynamicDisplayInfo.h>
 #include <utils/String8.h>
 #include <functional>
@@ -276,7 +277,7 @@
 TEST_F(CredentialsTest, CaptureLayersTest) {
     setupBackgroundSurface();
     sp<GraphicBuffer> outBuffer;
-    std::function<status_t()> condition = [=]() {
+    std::function<status_t()> condition = [=, this]() {
         LayerCaptureArgs captureArgs;
         captureArgs.layerHandle = mBGSurfaceControl->getHandle();
         captureArgs.sourceCrop = {0, 0, 1, 1};
@@ -396,6 +397,56 @@
     }
 }
 
+TEST_F(CredentialsTest, DisplayTransactionPermissionTest) {
+    const auto display = getFirstDisplayToken();
+
+    ui::DisplayState displayState;
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    const ui::Rotation initialOrientation = displayState.orientation;
+
+    // Set display orientation from an untrusted process. This should fail silently.
+    {
+        UIDFaker f{AID_BIN};
+        Transaction transaction;
+        Rect layerStackRect;
+        Rect displayRect;
+        transaction.setDisplayProjection(display, initialOrientation + ui::ROTATION_90,
+                                         layerStackRect, displayRect);
+        transaction.apply(/*synchronous=*/true);
+    }
+
+    // Verify that the display orientation did not change.
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    ASSERT_EQ(initialOrientation, displayState.orientation);
+
+    // Set display orientation from a trusted process.
+    {
+        UIDFaker f{AID_SYSTEM};
+        Transaction transaction;
+        Rect layerStackRect;
+        Rect displayRect;
+        transaction.setDisplayProjection(display, initialOrientation + ui::ROTATION_90,
+                                         layerStackRect, displayRect);
+        transaction.apply(/*synchronous=*/true);
+    }
+
+    // Verify that the display orientation did change.
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    ASSERT_EQ(initialOrientation + ui::ROTATION_90, displayState.orientation);
+
+    // Reset orientation
+    {
+        UIDFaker f{AID_SYSTEM};
+        Transaction transaction;
+        Rect layerStackRect;
+        Rect displayRect;
+        transaction.setDisplayProjection(display, initialOrientation, layerStackRect, displayRect);
+        transaction.apply(/*synchronous=*/true);
+    }
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    ASSERT_EQ(initialOrientation, displayState.orientation);
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 98d5754..bc35639 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -84,6 +84,7 @@
         "FrameTimelineTest.cpp",
         "GameModeTest.cpp",
         "HWComposerTest.cpp",
+        "JankTrackerTest.cpp",
         "OneShotTimerTest.cpp",
         "LayerHistoryTest.cpp",
         "LayerHistoryIntegrationTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 08973de..cdd77fe 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -70,6 +70,7 @@
 
 using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
 using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+using namespace ftl::flag_operators;
 
 constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
 constexpr hal::HWLayerId HWC_LAYER = 5000;
@@ -197,8 +198,11 @@
     const Rect sourceCrop(0, 0, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT);
     constexpr bool regionSampling = false;
 
-    auto renderArea = DisplayRenderArea::create(mDisplay, sourceCrop, sourceCrop.getSize(),
-                                                ui::Dataspace::V0_SRGB, true, true);
+    auto renderArea =
+            DisplayRenderArea::create(mDisplay, sourceCrop, sourceCrop.getSize(),
+                                      ui::Dataspace::V0_SRGB,
+                                      RenderArea::Options::CAPTURE_SECURE_LAYERS |
+                                              RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION);
 
     auto traverseLayers = [this](const LayerVector::Visitor& visitor) {
         return mFlinger.traverseLayersInLayerStack(mDisplay->getLayerStack(),
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index dac9265..9be0fc3 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -15,6 +15,8 @@
  */
 
 #include <common/test/FlagUtils.h>
+#include "BackgroundExecutor.h"
+#include "Jank/JankTracker.h"
 #include "com_android_graphics_surfaceflinger_flags.h"
 #include "gmock/gmock-spec-builders.h"
 #include "mock/MockTimeStats.h"
@@ -90,8 +92,12 @@
         mTraceCookieCounter = &mFrameTimeline->mTraceCookieCounter;
         maxDisplayFrames = &mFrameTimeline->mMaxDisplayFrames;
         maxTokens = mTokenManager->kMaxTokens;
+
+        JankTracker::clearAndStartCollectingAllJankDataForTesting();
     }
 
+    void TearDown() override { JankTracker::clearAndStopCollectingAllJankDataForTesting(); }
+
     // Each tracing session can be used for a single block of Start -> Stop.
     static std::unique_ptr<perfetto::TracingSession> getTracingSessionForTest() {
         perfetto::TraceConfig cfg;
@@ -175,6 +181,16 @@
                 [&](FrameTimelineDataSource::TraceContext ctx) { ctx.Flush(); });
     }
 
+    std::vector<gui::JankData> getLayerOneJankData() {
+        BackgroundExecutor::getLowPriorityInstance().flushQueue();
+        return JankTracker::getCollectedJankDataForTesting(sLayerIdOne);
+    }
+
+    std::vector<gui::JankData> getLayerTwoJankData() {
+        BackgroundExecutor::getLowPriorityInstance().flushQueue();
+        return JankTracker::getCollectedJankDataForTesting(sLayerIdTwo);
+    }
+
     std::shared_ptr<mock::TimeStats> mTimeStats;
     std::unique_ptr<impl::FrameTimeline> mFrameTimeline;
     impl::TokenManager* mTokenManager;
@@ -339,6 +355,9 @@
     EXPECT_NE(surfaceFrame1->getJankSeverityType(), std::nullopt);
     EXPECT_NE(surfaceFrame2->getJankType(), std::nullopt);
     EXPECT_NE(surfaceFrame2->getJankSeverityType(), std::nullopt);
+
+    EXPECT_EQ(getLayerOneJankData().size(), 1u);
+    EXPECT_EQ(getLayerTwoJankData().size(), 1u);
 }
 
 TEST_F(FrameTimelineTest, displayFrameSkippedComposition) {
@@ -446,6 +465,8 @@
     // The window should have slided by 1 now and the previous 0th display frame
     // should have been removed from the deque
     EXPECT_EQ(compareTimelineItems(displayFrame0->getActuals(), TimelineItem(52, 57, 62)), true);
+
+    EXPECT_EQ(getLayerOneJankData().size(), *maxDisplayFrames);
 }
 
 TEST_F(FrameTimelineTest, surfaceFrameEndTimeAcquireFenceAfterQueue) {
@@ -458,6 +479,16 @@
     EXPECT_EQ(surfaceFrame->getActuals().endTime, 456);
 }
 
+TEST_F(FrameTimelineTest, surfaceFrameEndTimeAcquireFenceUnsignaled) {
+    auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, 0, sLayerIdOne,
+                                                                   "acquireFenceAfterQueue",
+                                                                   "acquireFenceAfterQueue",
+                                                                   /*isBuffer*/ true, sGameMode);
+    surfaceFrame->setActualQueueTime(123);
+    surfaceFrame->setAcquireFenceTime(Fence::SIGNAL_TIME_PENDING);
+    EXPECT_EQ(surfaceFrame->getActuals().endTime, 123);
+}
+
 TEST_F(FrameTimelineTest, surfaceFrameEndTimeAcquireFenceBeforeQueue) {
     auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, 0, sLayerIdOne,
                                                                    "acquireFenceAfterQueue",
@@ -575,6 +606,8 @@
     presentFence1->signalForTest(70);
 
     mFrameTimeline->setSfPresent(59, presentFence1);
+
+    EXPECT_EQ(getLayerOneJankData().size(), 0u);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) {
@@ -603,6 +636,10 @@
     presentFence1->signalForTest(70);
 
     mFrameTimeline->setSfPresent(62, presentFence1);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::SurfaceFlingerCpuDeadlineMissed);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfGpu) {
@@ -633,6 +670,10 @@
     presentFence1->signalForTest(70);
 
     mFrameTimeline->setSfPresent(59, presentFence1, gpuFence1);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::SurfaceFlingerGpuDeadlineMissed);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsDisplayMiss) {
@@ -661,6 +702,10 @@
     mFrameTimeline->setSfPresent(56, presentFence1);
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::DisplayHAL);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::DisplayHAL);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) {
@@ -691,6 +736,10 @@
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Partial);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::AppDeadlineMissed);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfScheduling) {
@@ -721,6 +770,10 @@
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::SurfaceFlingerScheduling);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::SurfaceFlingerScheduling);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfPredictionError) {
@@ -751,6 +804,10 @@
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::PredictionError);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Partial);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::PredictionError);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppBufferStuffing) {
@@ -782,6 +839,10 @@
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::BufferStuffing);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::BufferStuffing);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMissWithRenderRate) {
@@ -814,6 +875,10 @@
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::AppDeadlineMissed);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_displayFramePredictionExpiredPresentsSurfaceFrame) {
@@ -858,6 +923,10 @@
     EXPECT_EQ(surfaceFrame1->getActuals().presentTime, 90);
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::Unknown | JankType::AppDeadlineMissed);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::Unknown | JankType::AppDeadlineMissed);
 }
 
 /*
@@ -1789,6 +1858,10 @@
     EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame->getJankType(), JankType::None);
     EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::None);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::None);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishEarlyPresent) {
diff --git a/services/surfaceflinger/tests/unittests/JankTrackerTest.cpp b/services/surfaceflinger/tests/unittests/JankTrackerTest.cpp
new file mode 100644
index 0000000..2941a14
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/JankTrackerTest.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/gui/BnJankListener.h>
+#include <binder/IInterface.h>
+#include "BackgroundExecutor.h"
+#include "Jank/JankTracker.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android {
+
+namespace {
+
+using namespace testing;
+
+class MockJankListener : public gui::BnJankListener {
+public:
+    MockJankListener() = default;
+    ~MockJankListener() override = default;
+
+    MOCK_METHOD(binder::Status, onJankData, (const std::vector<gui::JankData>& jankData),
+                (override));
+};
+
+} // anonymous namespace
+
+class JankTrackerTest : public Test {
+public:
+    JankTrackerTest() {}
+
+    void SetUp() override { mListener = sp<StrictMock<MockJankListener>>::make(); }
+
+    void addJankListener(int32_t layerId) {
+        JankTracker::addJankListener(layerId, IInterface::asBinder(mListener));
+    }
+
+    void removeJankListener(int32_t layerId, int64_t after) {
+        JankTracker::removeJankListener(layerId, IInterface::asBinder(mListener), after);
+    }
+
+    void addJankData(int32_t layerId, int jankType) {
+        gui::JankData data;
+        data.frameVsyncId = mVsyncId++;
+        data.jankType = jankType;
+        data.frameIntervalNs = 8333333;
+        JankTracker::onJankData(layerId, data);
+    }
+
+    void flushBackgroundThread() { BackgroundExecutor::getLowPriorityInstance().flushQueue(); }
+
+    size_t listenerCount() { return JankTracker::sListenerCount; }
+
+    std::vector<gui::JankData> getCollectedJankData(int32_t layerId) {
+        return JankTracker::getCollectedJankDataForTesting(layerId);
+    }
+
+    sp<StrictMock<MockJankListener>> mListener = nullptr;
+    int64_t mVsyncId = 1000;
+};
+
+TEST_F(JankTrackerTest, jankDataIsTrackedAndPropagated) {
+    ASSERT_EQ(listenerCount(), 0u);
+
+    EXPECT_CALL(*mListener.get(), onJankData(SizeIs(3)))
+            .WillOnce([](const std::vector<gui::JankData>& jankData) {
+                EXPECT_EQ(jankData[0].frameVsyncId, 1000);
+                EXPECT_EQ(jankData[0].jankType, 1);
+                EXPECT_EQ(jankData[0].frameIntervalNs, 8333333);
+
+                EXPECT_EQ(jankData[1].frameVsyncId, 1001);
+                EXPECT_EQ(jankData[1].jankType, 2);
+                EXPECT_EQ(jankData[1].frameIntervalNs, 8333333);
+
+                EXPECT_EQ(jankData[2].frameVsyncId, 1002);
+                EXPECT_EQ(jankData[2].jankType, 3);
+                EXPECT_EQ(jankData[2].frameIntervalNs, 8333333);
+                return binder::Status::ok();
+            });
+    EXPECT_CALL(*mListener.get(), onJankData(SizeIs(2)))
+            .WillOnce([](const std::vector<gui::JankData>& jankData) {
+                EXPECT_EQ(jankData[0].frameVsyncId, 1003);
+                EXPECT_EQ(jankData[0].jankType, 4);
+                EXPECT_EQ(jankData[0].frameIntervalNs, 8333333);
+
+                EXPECT_EQ(jankData[1].frameVsyncId, 1004);
+                EXPECT_EQ(jankData[1].jankType, 5);
+                EXPECT_EQ(jankData[1].frameIntervalNs, 8333333);
+
+                return binder::Status::ok();
+            });
+
+    addJankListener(123);
+    addJankData(123, 1);
+    addJankData(123, 2);
+    addJankData(123, 3);
+    JankTracker::flushJankData(123);
+    addJankData(123, 4);
+    removeJankListener(123, mVsyncId);
+    addJankData(123, 5);
+    JankTracker::flushJankData(123);
+    addJankData(123, 6);
+    JankTracker::flushJankData(123);
+    removeJankListener(123, 0);
+
+    flushBackgroundThread();
+}
+
+TEST_F(JankTrackerTest, jankDataIsAutomaticallyFlushedInBatches) {
+    ASSERT_EQ(listenerCount(), 0u);
+
+    // needs to be larger than kJankDataBatchSize in JankTracker.cpp.
+    constexpr size_t kNumberOfJankDataToSend = 234;
+
+    size_t jankDataReceived = 0;
+    size_t numBatchesReceived = 0;
+
+    EXPECT_CALL(*mListener.get(), onJankData(_))
+            .WillRepeatedly([&](const std::vector<gui::JankData>& jankData) {
+                jankDataReceived += jankData.size();
+                numBatchesReceived++;
+                return binder::Status::ok();
+            });
+
+    addJankListener(123);
+    for (size_t i = 0; i < kNumberOfJankDataToSend; i++) {
+        addJankData(123, 0);
+    }
+
+    flushBackgroundThread();
+    // Check that we got some data, without explicitly flushing.
+    EXPECT_GT(jankDataReceived, 0u);
+    EXPECT_GT(numBatchesReceived, 0u);
+    EXPECT_LT(numBatchesReceived, jankDataReceived); // batches should be > size 1.
+
+    removeJankListener(123, 0);
+    JankTracker::flushJankData(123);
+    flushBackgroundThread();
+    EXPECT_EQ(jankDataReceived, kNumberOfJankDataToSend);
+}
+
+TEST_F(JankTrackerTest, jankListenerIsRemovedWhenReturningNullError) {
+    ASSERT_EQ(listenerCount(), 0u);
+
+    EXPECT_CALL(*mListener.get(), onJankData(SizeIs(3)))
+            .WillOnce(Return(binder::Status::fromExceptionCode(binder::Status::EX_NULL_POINTER)));
+
+    addJankListener(123);
+    addJankData(123, 1);
+    addJankData(123, 2);
+    addJankData(123, 3);
+    JankTracker::flushJankData(123);
+    addJankData(123, 4);
+    addJankData(123, 5);
+    JankTracker::flushJankData(123);
+    flushBackgroundThread();
+
+    EXPECT_EQ(listenerCount(), 0u);
+}
+
+TEST_F(JankTrackerTest, jankDataIsDroppedIfNobodyIsListening) {
+    ASSERT_EQ(listenerCount(), 0u);
+
+    addJankData(123, 1);
+    addJankData(123, 2);
+    addJankData(123, 3);
+    flushBackgroundThread();
+
+    EXPECT_EQ(getCollectedJankData(123).size(), 0u);
+}
+
+TEST_F(JankTrackerTest, listenerCountTracksRegistrations) {
+    ASSERT_EQ(listenerCount(), 0u);
+
+    addJankListener(123);
+    addJankListener(456);
+    flushBackgroundThread();
+    EXPECT_EQ(listenerCount(), 2u);
+
+    removeJankListener(123, 0);
+    JankTracker::flushJankData(123);
+    removeJankListener(456, 0);
+    JankTracker::flushJankData(456);
+    flushBackgroundThread();
+    EXPECT_EQ(listenerCount(), 0u);
+}
+
+TEST_F(JankTrackerTest, listenerCountIsAccurateOnDuplicateRegistration) {
+    ASSERT_EQ(listenerCount(), 0u);
+
+    addJankListener(123);
+    addJankListener(123);
+    flushBackgroundThread();
+    EXPECT_EQ(listenerCount(), 1u);
+
+    removeJankListener(123, 0);
+    JankTracker::flushJankData(123);
+    flushBackgroundThread();
+    EXPECT_EQ(listenerCount(), 0u);
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 8b9ac93..54d4659 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -291,13 +291,106 @@
     transactions.back().states.front().layerId = 1;
     transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
     mLifecycleManager.applyTransactions(transactions);
-    EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::GameMode);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(),
+              RequestedLayerState::Changes::GameMode | RequestedLayerState::Changes::Metadata);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
     EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged);
     EXPECT_EQ(static_cast<int32_t>(getSnapshot(1)->gameMode), 42);
     EXPECT_EQ(static_cast<int32_t>(getSnapshot(11)->gameMode), 42);
 }
 
+TEST_F(LayerSnapshotTest, UpdateMetadata) {
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
+    // This test focuses on metadata used by ARC++ to ensure LayerMetadata is updated correctly,
+    // and not using stale data.
+    transactions.back().states.front().state.metadata = LayerMetadata();
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_UID, 123);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_WINDOW_TYPE, 234);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_TASK_ID, 345);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_MOUSE_CURSOR, 456);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_ACCESSIBILITY_ID, 567);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_PID, 678);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_CALLING_UID, 789);
+
+    transactions.back().states.front().layerId = 1;
+    transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
+
+    mLifecycleManager.applyTransactions(transactions);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::Metadata);
+
+    // Setting includeMetadata=true to ensure metadata update is applied to LayerSnapshot
+    LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                    .layerLifecycleManager = mLifecycleManager,
+                                    .includeMetadata = true,
+                                    .displays = mFrontEndDisplayInfos,
+                                    .globalShadowSettings = globalShadowSettings,
+                                    .supportsBlur = true,
+                                    .supportedLayerGenericMetadata = {},
+                                    .genericLayerMetadataKeyMap = {}};
+    update(mSnapshotBuilder, args);
+
+    EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_UID, -1), 123);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_WINDOW_TYPE, -1), 234);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_TASK_ID, -1), 345);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_MOUSE_CURSOR, -1), 456);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_ACCESSIBILITY_ID, -1), 567);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_PID, -1), 678);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789);
+}
+
+TEST_F(LayerSnapshotTest, UpdateMetadataOfHiddenLayers) {
+    hideLayer(1);
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
+    // This test focuses on metadata used by ARC++ to ensure LayerMetadata is updated correctly,
+    // and not using stale data.
+    transactions.back().states.front().state.metadata = LayerMetadata();
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_UID, 123);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_WINDOW_TYPE, 234);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_TASK_ID, 345);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_MOUSE_CURSOR, 456);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_ACCESSIBILITY_ID, 567);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_PID, 678);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_CALLING_UID, 789);
+
+    transactions.back().states.front().layerId = 1;
+    transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
+
+    mLifecycleManager.applyTransactions(transactions);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(),
+              RequestedLayerState::Changes::Metadata | RequestedLayerState::Changes::Visibility |
+                      RequestedLayerState::Changes::VisibleRegion |
+                      RequestedLayerState::Changes::AffectsChildren);
+
+    // Setting includeMetadata=true to ensure metadata update is applied to LayerSnapshot
+    LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                    .layerLifecycleManager = mLifecycleManager,
+                                    .includeMetadata = true,
+                                    .displays = mFrontEndDisplayInfos,
+                                    .globalShadowSettings = globalShadowSettings,
+                                    .supportsBlur = true,
+                                    .supportedLayerGenericMetadata = {},
+                                    .genericLayerMetadataKeyMap = {}};
+    update(mSnapshotBuilder, args);
+
+    EXPECT_EQ(static_cast<int64_t>(getSnapshot(1)->clientChanges),
+              layer_state_t::eMetadataChanged | layer_state_t::eFlagsChanged);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_UID, -1), 123);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_WINDOW_TYPE, -1), 234);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_TASK_ID, -1), 345);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_MOUSE_CURSOR, -1), 456);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_ACCESSIBILITY_ID, -1), 567);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_PID, -1), 678);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789);
+}
+
 TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) {
     // ROOT
     // ├── 1
@@ -1304,6 +1397,17 @@
     EXPECT_TRUE(foundInputLayer);
 }
 
+TEST_F(LayerSnapshotTest, ForEachSnapshotsWithPredicate) {
+    std::vector<uint32_t> visitedUniqueSequences;
+    mSnapshotBuilder.forEachSnapshot(
+            [&](const std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                visitedUniqueSequences.push_back(snapshot->uniqueSequence);
+            },
+            [](const frontend::LayerSnapshot& snapshot) { return snapshot.uniqueSequence == 111; });
+    EXPECT_EQ(visitedUniqueSequences.size(), 1u);
+    EXPECT_EQ(visitedUniqueSequences[0], 111u);
+}
+
 TEST_F(LayerSnapshotTest, canOccludePresentation) {
     setFlags(12, layer_state_t::eCanOccludePresentation, layer_state_t::eCanOccludePresentation);
     LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index cf9a7d3..06c4e30 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -1562,7 +1562,7 @@
             // When frame rates get an equal score, the lower is chosen, unless there are Max votes.
             {0_Hz, FrameRateCategory::High, 90_Hz},
             {0_Hz, FrameRateCategory::Normal, 60_Hz},
-            {0_Hz, FrameRateCategory::Low, 30_Hz},
+            {0_Hz, FrameRateCategory::Low, 60_Hz},
             {0_Hz, FrameRateCategory::NoPreference, 60_Hz},
 
             // Cases that have both desired frame rate and frame rate category requirements.
@@ -1609,6 +1609,77 @@
     }
 }
 
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_120_vrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    // Device with VRR config mode
+    auto selector = createSelector(kVrrMode_120, kModeId120);
+
+    struct Case {
+        // Params
+        Fps desiredFrameRate = 0_Hz;
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+    };
+
+    // Prepare a table with the vote and the expected refresh rate
+    const std::initializer_list<Case> testCases = {
+            // Cases that only have frame rate category requirements, but no desired frame rate.
+            // When frame rates get an equal score, the lower is chosen, unless there are Max votes.
+            {0_Hz, FrameRateCategory::High, 120_Hz},
+            {0_Hz, FrameRateCategory::Normal, 60_Hz},
+            {0_Hz, FrameRateCategory::Low, 48_Hz},
+            {0_Hz, FrameRateCategory::NoPreference, 120_Hz},
+
+            // Cases that have both desired frame rate and frame rate category requirements.
+            {24_Hz, FrameRateCategory::High, 120_Hz},
+            {30_Hz, FrameRateCategory::High, 120_Hz},
+            {12_Hz, FrameRateCategory::Normal, 60_Hz},
+            {24_Hz, FrameRateCategory::Low, 48_Hz},
+            {30_Hz, FrameRateCategory::NoPreference, 30_Hz},
+
+            // Cases that only have desired frame rate.
+            {30_Hz, FrameRateCategory::Default, 30_Hz},
+    };
+
+    for (auto testCase : testCases) {
+        std::vector<LayerRequirement> layers;
+        ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
+              to_string(testCase.desiredFrameRate).c_str(),
+              ftl::enum_string(testCase.frameRateCategory).c_str());
+
+        if (testCase.desiredFrameRate.isValid()) {
+            std::stringstream ss;
+            ss << to_string(testCase.desiredFrameRate) << "ExplicitDefault";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitDefault,
+                                      .desiredRefreshRate = testCase.desiredFrameRate,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        if (testCase.frameRateCategory != FrameRateCategory::Default) {
+            std::stringstream ss;
+            ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitCategory,
+                                      .frameRateCategory = testCase.frameRateCategory,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        EXPECT_EQ(testCase.expectedFrameRate, selector.getBestFrameRateMode(layers).fps)
+                << "Did not get expected frame rate for frameRate="
+                << to_string(testCase.desiredFrameRate)
+                << " category=" << ftl::enum_string(testCase.frameRateCategory);
+    }
+}
+
 TEST_P(RefreshRateSelectorTest,
        getBestFrameRateMode_withFrameRateCategoryMultiLayers_30_60_90_120) {
     auto selector = createSelector(makeModes(kMode30, kMode60, kMode90, kMode120), kModeId60);
@@ -2140,14 +2211,14 @@
             // These layers may switch modes because smoothSwitchOnly=false.
             {FrameRateCategory::Default, false, 120_Hz, kModeId120},
             {FrameRateCategory::NoPreference, false, 120_Hz, kModeId120},
-            {FrameRateCategory::Low, false, 30_Hz, kModeId60},
+            {FrameRateCategory::Low, false, 60_Hz, kModeId60},
             {FrameRateCategory::Normal, false, 60_Hz, kModeId60},
             {FrameRateCategory::High, false, 120_Hz, kModeId120},
 
             // These layers cannot change mode due to smoothSwitchOnly, and will definitely use
             // active mode (120Hz).
             {FrameRateCategory::NoPreference, true, 120_Hz, kModeId120},
-            {FrameRateCategory::Low, true, 40_Hz, kModeId120},
+            {FrameRateCategory::Low, true, 120_Hz, kModeId120},
             {FrameRateCategory::Normal, true, 120_Hz, kModeId120},
             {FrameRateCategory::High, true, 120_Hz, kModeId120},
     };
@@ -2207,13 +2278,13 @@
             {FrameRateCategory::Default, false, 120_Hz},
             // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
             {FrameRateCategory::NoPreference, false, 120_Hz},
-            {FrameRateCategory::Low, false, 30_Hz},
+            {FrameRateCategory::Low, false, 48_Hz},
             {FrameRateCategory::Normal, false, 60_Hz},
             {FrameRateCategory::High, false, 120_Hz},
             {FrameRateCategory::Default, true, 120_Hz},
             // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
             {FrameRateCategory::NoPreference, true, 120_Hz},
-            {FrameRateCategory::Low, true, 30_Hz},
+            {FrameRateCategory::Low, true, 48_Hz},
             {FrameRateCategory::Normal, true, 60_Hz},
             {FrameRateCategory::High, true, 120_Hz},
     };
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 4fb0690..fc54a8b 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -343,12 +343,15 @@
 }
 
 TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
+    constexpr PhysicalDisplayId kActiveDisplayId = kDisplayId1;
     mScheduler->registerDisplay(kDisplayId1,
                                 std::make_shared<RefreshRateSelector>(kDisplay1Modes,
-                                                                      kDisplay1Mode60->getId()));
+                                                                      kDisplay1Mode60->getId()),
+                                kActiveDisplayId);
     mScheduler->registerDisplay(kDisplayId2,
                                 std::make_shared<RefreshRateSelector>(kDisplay2Modes,
-                                                                      kDisplay2Mode60->getId()));
+                                                                      kDisplay2Mode60->getId()),
+                                kActiveDisplayId);
 
     mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
     mScheduler->setDisplayPowerMode(kDisplayId2, hal::PowerMode::ON);
@@ -411,10 +414,10 @@
     {
         // The kDisplayId3 does not support 120Hz, The pacesetter display rate is chosen to be 120
         // Hz. In this case only the display kDisplayId3 choose 60Hz as it does not support 120Hz.
-        mScheduler
-                ->registerDisplay(kDisplayId3,
-                                  std::make_shared<RefreshRateSelector>(kDisplay3Modes,
-                                                                        kDisplay3Mode60->getId()));
+        mScheduler->registerDisplay(kDisplayId3,
+                                    std::make_shared<RefreshRateSelector>(kDisplay3Modes,
+                                                                          kDisplay3Mode60->getId()),
+                                    kActiveDisplayId);
         mScheduler->setDisplayPowerMode(kDisplayId3, hal::PowerMode::ON);
 
         const GlobalSignals globalSignals = {.touch = true};
@@ -457,12 +460,15 @@
 }
 
 TEST_F(SchedulerTest, onFrameSignalMultipleDisplays) {
+    constexpr PhysicalDisplayId kActiveDisplayId = kDisplayId1;
     mScheduler->registerDisplay(kDisplayId1,
                                 std::make_shared<RefreshRateSelector>(kDisplay1Modes,
-                                                                      kDisplay1Mode60->getId()));
+                                                                      kDisplay1Mode60->getId()),
+                                kActiveDisplayId);
     mScheduler->registerDisplay(kDisplayId2,
                                 std::make_shared<RefreshRateSelector>(kDisplay2Modes,
-                                                                      kDisplay2Mode60->getId()));
+                                                                      kDisplay2Mode60->getId()),
+                                kActiveDisplayId);
 
     using VsyncIds = std::vector<std::pair<PhysicalDisplayId, VsyncId>>;
 
@@ -585,7 +591,8 @@
                                 mFlinger.getTimeStats(),
                                 mSchedulerCallback};
 
-    scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker);
+    scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, std::nullopt,
+                              vrrTracker);
     vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
     scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false);
     vrrTracker->addVsyncTimestamp(0);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 0c3e875..4b0a7c3 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -42,6 +42,47 @@
 using android::hardware::graphics::composer::V2_4::Error;
 using android::hardware::graphics::composer::V2_4::VsyncPeriodChangeTimeline;
 
+MATCHER_P2(ModeSettledTo, dmc, modeId, "") {
+    const auto displayId = arg->getPhysicalId();
+
+    if (const auto desiredOpt = dmc->getDesiredMode(displayId)) {
+        *result_listener << "Unsettled desired mode "
+                         << ftl::to_underlying(desiredOpt->mode.modePtr->getId());
+        return false;
+    }
+
+    if (dmc->getActiveMode(displayId).modePtr->getId() != modeId) {
+        *result_listener << "Settled to unexpected active mode " << ftl::to_underlying(modeId);
+        return false;
+    }
+
+    return true;
+}
+
+MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") {
+    const auto displayId = arg->getPhysicalId();
+    auto& dmc = flinger->mutableDisplayModeController();
+
+    if (!dmc.getDesiredMode(displayId)) {
+        *result_listener << "No desired mode";
+        return false;
+    }
+
+    if (dmc.getDesiredMode(displayId)->mode.modePtr->getId() != modeId) {
+        *result_listener << "Unexpected desired mode " << ftl::to_underlying(modeId);
+        return false;
+    }
+
+    // VsyncModulator should react to mode switches on the pacesetter display.
+    if (displayId == flinger->scheduler()->pacesetterDisplayId() &&
+        !flinger->scheduler()->vsyncModulator().isVsyncConfigEarly()) {
+        *result_listener << "VsyncModulator did not shift to early phase";
+        return false;
+    }
+
+    return true;
+}
+
 class DisplayModeSwitchingTest : public DisplayTransactionTest {
 public:
     void SetUp() override {
@@ -58,8 +99,7 @@
 
         setupScheduler(selectorPtr);
 
-        mFlinger.onComposerHalHotplugEvent(PrimaryDisplayVariant::HWC_DISPLAY_ID,
-                                           DisplayHotplugEvent::CONNECTED);
+        mFlinger.onComposerHalHotplugEvent(kInnerDisplayHwcId, DisplayHotplugEvent::CONNECTED);
         mFlinger.configureAndCommit();
 
         auto vsyncController = std::make_unique<mock::VsyncController>();
@@ -87,8 +127,13 @@
     static constexpr HWDisplayId kInnerDisplayHwcId = PrimaryDisplayVariant::HWC_DISPLAY_ID;
     static constexpr HWDisplayId kOuterDisplayHwcId = kInnerDisplayHwcId + 1;
 
+    static constexpr PhysicalDisplayId kOuterDisplayId = PhysicalDisplayId::fromPort(254u);
+
     auto injectOuterDisplay() {
-        constexpr PhysicalDisplayId kOuterDisplayId = PhysicalDisplayId::fromPort(254u);
+        // For the inner display, this is handled by setupHwcHotplugCallExpectations.
+        EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
+                .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
+                                Return(hal::V2_4::Error::NONE)));
 
         constexpr bool kIsPrimary = false;
         TestableSurfaceFlinger::FakeHwcDisplayInjector(kOuterDisplayId, hal::DisplayType::PHYSICAL,
@@ -169,129 +214,139 @@
                             TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp);
 }
 
-TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithRefreshRequired) {
-    ftl::FakeGuard guard(kMainThreadContext);
+TEST_F(DisplayModeSwitchingTest, changeRefreshRateWithRefreshRequired) {
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId60));
 
-    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz)));
 
-    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
-
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90, false, 0, 120));
-
-    ASSERT_TRUE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId90);
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
-    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90);
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
 
     mFlinger.commit();
-
     Mock::VerifyAndClearExpectations(mComposer);
 
-    EXPECT_TRUE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
 
     // Verify that the next commit will complete the mode change and send
     // a onModeChanged event to the framework.
 
     EXPECT_CALL(*mAppEventThread,
                 onModeChanged(scheduler::FrameRateMode{90_Hz, ftl::as_non_null(kMode90)}));
+
     mFlinger.commit();
     Mock::VerifyAndClearExpectations(mAppEventThread);
 
-    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId90);
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId90));
 }
 
-TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithoutRefreshRequired) {
-    ftl::FakeGuard guard(kMainThreadContext);
+TEST_F(DisplayModeSwitchingTest, changeRefreshRateWithoutRefreshRequired) {
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId60));
 
-    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
+    constexpr bool kAllowGroupSwitching = true;
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(
+                      mDisplay->getDisplayToken().promote(),
+                      mock::createDisplayModeSpecs(kModeId90, 120_Hz, kAllowGroupSwitching)));
 
-    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
-
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90, true, 0, 120));
-
-    ASSERT_TRUE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId90);
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     // and complete the mode change.
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = false};
-    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90);
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
 
     EXPECT_CALL(*mAppEventThread,
                 onModeChanged(scheduler::FrameRateMode{90_Hz, ftl::as_non_null(kMode90)}));
 
     mFlinger.commit();
 
-    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId90);
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId90));
 }
 
-TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) {
-    ftl::FakeGuard guard(kMainThreadContext);
+TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnTwoDisplaysWithoutRefreshRequired) {
+    const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
-    // Test that if we call setDesiredDisplayModeSpecs while a previous mode change
-    // is still being processed the later call will be respected.
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
-    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz,
+                                                                               true)));
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId60, 60_Hz,
+                                                                               true)));
 
-    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
-
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90, false, 0, 120));
-
-    const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
-    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90);
-
-    mFlinger.commit();
-
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId120, false, 0, 180));
-
-    ASSERT_TRUE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId120);
-
-    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId120);
-
-    mFlinger.commit();
-
-    ASSERT_TRUE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId120);
-
-    mFlinger.commit();
-
-    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId120);
-}
-
-TEST_F(DisplayModeSwitchingTest, changeResolutionOnActiveDisplayWithoutRefreshRequired) {
-    ftl::FakeGuard guard(kMainThreadContext);
-
-    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
-
-    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
-
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90_4K, false, 0, 120));
-
-    ASSERT_TRUE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId90_4K);
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60);
+    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     // and complete the mode change.
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = false};
-    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90_4K);
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
+    EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60);
+
+    EXPECT_CALL(*mAppEventThread, onModeChanged(_)).Times(2);
+
+    mFlinger.commit();
+
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
+}
+
+TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) {
+    // Test that if we call setDesiredDisplayModeSpecs while a previous mode change
+    // is still being processed the later call will be respected.
+
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId60));
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz)));
+
+    const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
+
+    mFlinger.commit();
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId120,
+                                                                               180_Hz)));
+
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId120));
+
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId120);
+
+    mFlinger.commit();
+
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId120));
+
+    mFlinger.commit();
+
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId120));
+}
+
+TEST_F(DisplayModeSwitchingTest, changeResolutionWithoutRefreshRequired) {
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId60));
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId90_4K,
+                                                                               120_Hz)));
+
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90_4K));
+
+    // Verify that next commit will call setActiveConfigWithConstraints in HWC
+    // and complete the mode change.
+    const VsyncPeriodChangeTimeline timeline{.refreshRequired = false};
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90_4K);
 
     EXPECT_CALL(*mAppEventThread, onHotplugReceived(mDisplayId, true));
 
@@ -310,61 +365,12 @@
 
     mFlinger.commit();
 
-    EXPECT_FALSE(dmc().getDesiredMode(mDisplayId));
-    EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId90_4K);
-}
-
-MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") {
-    const auto displayId = arg->getPhysicalId();
-    auto& dmc = flinger->mutableDisplayModeController();
-
-    if (!dmc.getDesiredMode(displayId)) {
-        *result_listener << "No desired mode";
-        return false;
-    }
-
-    if (dmc.getDesiredMode(displayId)->mode.modePtr->getId() != modeId) {
-        *result_listener << "Unexpected desired mode " << ftl::to_underlying(modeId);
-        return false;
-    }
-
-    // VsyncModulator should react to mode switches on the pacesetter display.
-    if (displayId == flinger->scheduler()->pacesetterDisplayId() &&
-        !flinger->scheduler()->vsyncModulator().isVsyncConfigEarly()) {
-        *result_listener << "VsyncModulator did not shift to early phase";
-        return false;
-    }
-
-    return true;
-}
-
-MATCHER_P2(ModeSettledTo, dmc, modeId, "") {
-    const auto displayId = arg->getPhysicalId();
-
-    if (const auto desiredOpt = dmc->getDesiredMode(displayId)) {
-        *result_listener << "Unsettled desired mode "
-                         << ftl::to_underlying(desiredOpt->mode.modePtr->getId());
-        return false;
-    }
-
-    ftl::FakeGuard guard(kMainThreadContext);
-
-    if (dmc->getActiveMode(displayId).modePtr->getId() != modeId) {
-        *result_listener << "Settled to unexpected active mode " << ftl::to_underlying(modeId);
-        return false;
-    }
-
-    return true;
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId90_4K));
 }
 
 TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) {
     SET_FLAG_FOR_TEST(flags::connected_display, true);
 
-    // For the inner display, this is handled by setupHwcHotplugCallExpectations.
-    EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
-            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
-                            Return(hal::V2_4::Error::NONE)));
-
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
@@ -381,13 +387,11 @@
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId90, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz)));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId60, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId60, 120_Hz)));
 
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
@@ -414,8 +418,7 @@
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId60, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId60, 120_Hz)));
 
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
     EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId60);
@@ -434,10 +437,6 @@
 TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) {
     SET_FLAG_FOR_TEST(flags::connected_display, true);
 
-    // For the inner display, this is handled by setupHwcHotplugCallExpectations.
-    EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
-            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
-                            Return(hal::V2_4::Error::NONE)));
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
@@ -454,13 +453,11 @@
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId90, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz)));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId60, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId60, 120_Hz)));
 
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
@@ -486,8 +483,7 @@
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId90, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz)));
 
     EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
 
@@ -511,11 +507,6 @@
 TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) {
     SET_FLAG_FOR_TEST(flags::connected_display, true);
 
-    // For the inner display, this is handled by setupHwcHotplugCallExpectations.
-    EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
-            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
-                            Return(hal::V2_4::Error::NONE)));
-
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
@@ -532,13 +523,11 @@
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId90, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz)));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId60, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId60, 120_Hz)));
 
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
@@ -566,8 +555,8 @@
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId120, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId120,
+                                                                               120_Hz)));
 
     EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId120);
 
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 198a5de..f063809 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -53,7 +53,7 @@
                       factory, selectorPtr->getActiveMode().fps, timeStats) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
-                        std::move(tracker));
+                        std::move(tracker), displayId);
 
         ON_CALL(*this, postMessage).WillByDefault([](sp<MessageHandler>&& handler) {
             // Execute task to prevent broken promise exception on destruction.
@@ -85,14 +85,16 @@
 
     void registerDisplay(
             PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+            std::optional<PhysicalDisplayId> activeDisplayIdOpt = {},
             std::shared_ptr<VSyncTracker> vsyncTracker = std::make_shared<mock::VSyncTracker>()) {
         registerDisplay(displayId, std::move(selectorPtr),
-                        std::make_unique<mock::VsyncController>(), vsyncTracker);
+                        std::make_unique<mock::VsyncController>(), vsyncTracker,
+                        activeDisplayIdOpt.value_or(displayId));
     }
 
     void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
                          std::unique_ptr<VsyncController> controller,
-                         std::shared_ptr<VSyncTracker> tracker) {
+                         std::shared_ptr<VSyncTracker> tracker, PhysicalDisplayId activeDisplayId) {
         ftl::FakeGuard guard(kMainThreadContext);
         Scheduler::registerDisplayInternal(displayId, std::move(selectorPtr),
                                            std::shared_ptr<VsyncSchedule>(
@@ -101,16 +103,12 @@
                                                                              mock::VSyncDispatch>(),
                                                                      std::move(controller),
                                                                      mockRequestHardwareVsync
-                                                                             .AsStdFunction())));
+                                                                             .AsStdFunction())),
+                                           activeDisplayId);
     }
 
     testing::MockFunction<void(PhysicalDisplayId, bool)> mockRequestHardwareVsync;
 
-    void unregisterDisplay(PhysicalDisplayId displayId) {
-        ftl::FakeGuard guard(kMainThreadContext);
-        Scheduler::unregisterDisplay(displayId);
-    }
-
     void setDisplayPowerMode(PhysicalDisplayId displayId, hal::PowerMode powerMode) {
         ftl::FakeGuard guard(kMainThreadContext);
         Scheduler::setDisplayPowerMode(displayId, powerMode);
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 007383b..b5b36be 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -528,7 +528,7 @@
 
     auto setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
             bool hasListenerCallbacks, std::vector<ListenerCallbacks>& listenerCallbacks,
@@ -1118,8 +1118,8 @@
                 if (mFlinger.scheduler() && mSchedulerRegistration) {
                     mFlinger.scheduler()->registerDisplay(*physicalId,
                                                           mCreationArgs.refreshRateSelector,
-                                                          std::move(controller),
-                                                          std::move(tracker));
+                                                          std::move(controller), std::move(tracker),
+                                                          mFlinger.mutableActiveDisplayId());
                 }
             }
 
diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
index 46733b9..0745f87 100644
--- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
@@ -302,58 +302,6 @@
         EXPECT_EQ(PresentState::Presented, bufferSurfaceFrameTX->getPresentState());
     }
 
-    void PendingSurfaceFramesRemovedAfterClassification() {
-        sp<Layer> layer = createLayer();
-
-        sp<Fence> fence1(sp<Fence>::make());
-        auto acquireFence1 = fenceFactory.createFenceTimeForTest(fence1);
-        BufferData bufferData;
-        bufferData.acquireFence = fence1;
-        bufferData.frameNumber = 1;
-        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
-        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
-        std::shared_ptr<renderengine::ExternalTexture> externalTexture1 = std::make_shared<
-                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
-                                                         1ULL /* bufferId */,
-                                                         HAL_PIXEL_FORMAT_RGBA_8888,
-                                                         0ULL /*usage*/);
-        FrameTimelineInfo ftInfo;
-        ftInfo.vsyncId = 1;
-        ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, ftInfo);
-        ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
-        const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
-
-        sp<Fence> fence2(sp<Fence>::make());
-        auto acquireFence2 = fenceFactory.createFenceTimeForTest(fence2);
-        bufferData.acquireFence = fence2;
-        bufferData.frameNumber = 1;
-        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
-        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
-        std::shared_ptr<renderengine::ExternalTexture> externalTexture2 = std::make_shared<
-                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
-                                                         1ULL /* bufferId */,
-                                                         HAL_PIXEL_FORMAT_RGBA_8888,
-                                                         0ULL /*usage*/);
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, ftInfo);
-        acquireFence2->signalForTest(12);
-
-        ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
-        auto presentedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
-
-        commitTransaction(layer.get());
-        layer->updateTexImage(15);
-
-        // Both the droppedSurfaceFrame and presentedSurfaceFrame should be in
-        // pendingJankClassifications.
-        EXPECT_EQ(2u, layer->mPendingJankClassifications.size());
-        presentedSurfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
-                                         /*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
-        layer->releasePendingBuffer(25);
-
-        EXPECT_EQ(0u, layer->mPendingJankClassifications.size());
-    }
-
     void BufferSurfaceFrame_ReplaceValidTokenBufferWithInvalidTokenBuffer() {
         sp<Layer> layer = createLayer();
 
@@ -445,8 +393,7 @@
 
     void MultipleCommitsBeforeLatch() {
         sp<Layer> layer = createLayer();
-        uint32_t surfaceFramesPendingClassification = 0;
-        std::vector<std::shared_ptr<frametimeline::SurfaceFrame>> bufferlessSurfaceFrames;
+        std::vector<std::shared_ptr<frametimeline::SurfaceFrame>> surfaceFrames;
         for (int i = 0; i < 10; i += 2) {
             sp<Fence> fence(sp<Fence>::make());
             BufferData bufferData;
@@ -469,51 +416,43 @@
             layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo2, 10);
             ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
             EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
-            auto& bufferlessSurfaceFrame =
-                    layer->mDrawingState.bufferlessSurfaceFramesTX.at(/*vsyncId*/ 2);
-            bufferlessSurfaceFrames.push_back(bufferlessSurfaceFrame);
+
+            surfaceFrames.push_back(layer->mDrawingState.bufferSurfaceFrameTX);
+            surfaceFrames.push_back(
+                    layer->mDrawingState.bufferlessSurfaceFramesTX.at(/*vsyncId*/ 2));
 
             commitTransaction(layer.get());
-            surfaceFramesPendingClassification += 2;
-            EXPECT_EQ(surfaceFramesPendingClassification,
-                      layer->mPendingJankClassifications.size());
         }
 
         auto presentedBufferSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
         layer->updateTexImage(15);
         // BufferlessSurfaceFrames are immediately set to presented and added to the DisplayFrame.
         // Since we don't have access to DisplayFrame here, trigger an onPresent directly.
-        for (auto& surfaceFrame : bufferlessSurfaceFrames) {
-            surfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
-                                    /*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
+        // The odd indices are the bufferless frames.
+        for (uint32_t i = 1; i < 10; i += 2) {
+            surfaceFrames[i]->onPresent(20, JankType::None, 90_Hz, 90_Hz,
+                                        /*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
         }
         presentedBufferSurfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
                                                /*displayDeadlineDelta*/ 0,
                                                /*displayPresentDelta*/ 0);
 
-        // There should be 10 bufferlessSurfaceFrames and 1 bufferSurfaceFrame
-        ASSERT_EQ(10u, surfaceFramesPendingClassification);
-        ASSERT_EQ(surfaceFramesPendingClassification, layer->mPendingJankClassifications.size());
-
         // For the frames upto 8, the bufferSurfaceFrame should have been dropped while the
         // bufferlessSurfaceFrame presented
         for (uint32_t i = 0; i < 8; i += 2) {
-            auto& bufferSurfaceFrame = layer->mPendingJankClassifications[i];
-            auto& bufferlessSurfaceFrame = layer->mPendingJankClassifications[i + 1];
+            auto bufferSurfaceFrame = surfaceFrames[i];
+            auto bufferlessSurfaceFrame = surfaceFrames[i + 1];
             EXPECT_EQ(bufferSurfaceFrame->getPresentState(), PresentState::Dropped);
             EXPECT_EQ(bufferlessSurfaceFrame->getPresentState(), PresentState::Presented);
         }
         {
-            auto& bufferSurfaceFrame = layer->mPendingJankClassifications[8u];
-            auto& bufferlessSurfaceFrame = layer->mPendingJankClassifications[9u];
+            auto bufferSurfaceFrame = surfaceFrames[8];
+            auto bufferlessSurfaceFrame = surfaceFrames[9];
             EXPECT_EQ(bufferSurfaceFrame->getPresentState(), PresentState::Presented);
             EXPECT_EQ(bufferlessSurfaceFrame->getPresentState(), PresentState::Presented);
         }
 
         layer->releasePendingBuffer(25);
-
-        // There shouldn't be any pending classifications. Everything should have been cleared.
-        EXPECT_EQ(0u, layer->mPendingJankClassifications.size());
     }
 };
 
@@ -541,10 +480,6 @@
     MultipleSurfaceFramesPresentedTogether();
 }
 
-TEST_F(TransactionSurfaceFrameTest, PendingSurfaceFramesRemovedAfterClassification) {
-    PendingSurfaceFramesRemovedAfterClassification();
-}
-
 TEST_F(TransactionSurfaceFrameTest,
        BufferSurfaceFrame_ReplaceValidTokenBufferWithInvalidTokenBuffer) {
     BufferSurfaceFrame_ReplaceValidTokenBufferWithInvalidTokenBuffer();
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 5109ea6..f36a8a6 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -673,6 +673,36 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
 }
 
+TEST_F(VSyncPredictorTest, setRenderRateWhenRenderRateGoesDown) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    SET_FLAG_FOR_TEST(flags::vrr_bugfix_24q4, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    Fps frameRate = Fps::fromPeriodNsecs(1000);
+    vrrTracker.setRenderRate(frameRate, /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+    frameRate = Fps::fromPeriodNsecs(3000);
+    vrrTracker.setRenderRate(frameRate, /*applyImmediately*/ false);
+    EXPECT_TRUE(vrrTracker.isVSyncInPhase(2000, frameRate));
+}
+
 TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediately) {
     SET_FLAG_FOR_TEST(flags::vrr_config, true);
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
index 7b18a82..4d35d4d 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
@@ -22,14 +22,13 @@
 
 namespace android::mock {
 
-inline gui::DisplayModeSpecs createDisplayModeSpecs(DisplayModeId defaultMode,
-                                                    bool allowGroupSwitching, float minFps,
-                                                    float maxFps) {
+inline gui::DisplayModeSpecs createDisplayModeSpecs(DisplayModeId defaultMode, Fps maxFps,
+                                                    bool allowGroupSwitching = false) {
     gui::DisplayModeSpecs specs;
     specs.defaultMode = ftl::to_underlying(defaultMode);
     specs.allowGroupSwitching = allowGroupSwitching;
-    specs.primaryRanges.physical.min = minFps;
-    specs.primaryRanges.physical.max = maxFps;
+    specs.primaryRanges.physical.min = 0.f;
+    specs.primaryRanges.physical.max = maxFps.getValue();
     specs.primaryRanges.render = specs.primaryRanges.physical;
     specs.appRequestRanges = specs.primaryRanges;
     return specs;
diff --git a/services/vibratorservice/Android.bp b/services/vibratorservice/Android.bp
index 2002bdf..503587f 100644
--- a/services/vibratorservice/Android.bp
+++ b/services/vibratorservice/Android.bp
@@ -33,19 +33,19 @@
     ],
 
     aidl: {
-       local_include_dirs: ["include"],
-       include_dirs: [
-           "hardware/interfaces/vibrator/aidl/android/hardware/vibrator",
-       ],
-       export_aidl_headers: true
+        local_include_dirs: ["include"],
+        include_dirs: [
+            "hardware/interfaces/vibrator/aidl/android/hardware/vibrator",
+        ],
+        export_aidl_headers: true,
     },
 
     shared_libs: [
-        "libbinder",
+        "libbinder_ndk",
         "libhidlbase",
         "liblog",
         "libutils",
-        "android.hardware.vibrator-V2-cpp",
+        "android.hardware.vibrator-V2-ndk",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
diff --git a/services/vibratorservice/VibratorHalController.cpp b/services/vibratorservice/VibratorHalController.cpp
index c1795f5..283a5f0 100644
--- a/services/vibratorservice/VibratorHalController.cpp
+++ b/services/vibratorservice/VibratorHalController.cpp
@@ -16,9 +16,9 @@
 
 #define LOG_TAG "VibratorHalController"
 
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+#include <android/binder_manager.h>
 #include <android/hardware/vibrator/1.3/IVibrator.h>
-#include <android/hardware/vibrator/IVibrator.h>
-#include <binder/IServiceManager.h>
 #include <hardware/vibrator.h>
 
 #include <utils/Log.h>
@@ -27,10 +27,10 @@
 #include <vibratorservice/VibratorHalController.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
-using android::hardware::vibrator::CompositeEffect;
-using android::hardware::vibrator::CompositePrimitive;
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
 
 using std::chrono::milliseconds;
 
@@ -38,7 +38,7 @@
 namespace V1_1 = android::hardware::vibrator::V1_1;
 namespace V1_2 = android::hardware::vibrator::V1_2;
 namespace V1_3 = android::hardware::vibrator::V1_3;
-namespace Aidl = android::hardware::vibrator;
+namespace Aidl = aidl::android::hardware::vibrator;
 
 namespace android {
 
@@ -53,10 +53,14 @@
         return nullptr;
     }
 
-    sp<Aidl::IVibrator> aidlHal = waitForVintfService<Aidl::IVibrator>();
-    if (aidlHal) {
-        ALOGV("Successfully connected to Vibrator HAL AIDL service.");
-        return std::make_shared<AidlHalWrapper>(std::move(scheduler), aidlHal);
+    auto serviceName = std::string(Aidl::IVibrator::descriptor) + "/default";
+    if (AServiceManager_isDeclared(serviceName.c_str())) {
+        std::shared_ptr<Aidl::IVibrator> hal = Aidl::IVibrator::fromBinder(
+                ndk::SpAIBinder(AServiceManager_waitForService(serviceName.c_str())));
+        if (hal) {
+            ALOGV("Successfully connected to Vibrator HAL AIDL service.");
+            return std::make_shared<AidlHalWrapper>(std::move(scheduler), std::move(hal));
+        }
     }
 
     sp<V1_0::IVibrator> halV1_0 = V1_0::IVibrator::getService();
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index f10ba44..abe78f0 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -16,8 +16,8 @@
 
 #define LOG_TAG "VibratorHalWrapper"
 
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 #include <android/hardware/vibrator/1.3/IVibrator.h>
-#include <android/hardware/vibrator/IVibrator.h>
 #include <hardware/vibrator.h>
 #include <cmath>
 
@@ -26,12 +26,12 @@
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
-using android::hardware::vibrator::Braking;
-using android::hardware::vibrator::CompositeEffect;
-using android::hardware::vibrator::CompositePrimitive;
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
-using android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::Braking;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::PrimitivePwle;
 
 using std::chrono::milliseconds;
 
@@ -39,7 +39,7 @@
 namespace V1_1 = android::hardware::vibrator::V1_1;
 namespace V1_2 = android::hardware::vibrator::V1_2;
 namespace V1_3 = android::hardware::vibrator::V1_3;
-namespace Aidl = android::hardware::vibrator;
+namespace Aidl = aidl::android::hardware::vibrator;
 
 namespace android {
 
@@ -200,7 +200,7 @@
 // -------------------------------------------------------------------------------------------------
 
 HalResult<void> AidlHalWrapper::ping() {
-    return HalResultFactory::fromStatus(IInterface::asBinder(getHal())->pingBinder());
+    return HalResultFactory::fromStatus(AIBinder_ping(getHal()->asBinder().get()));
 }
 
 void AidlHalWrapper::tryReconnect() {
@@ -208,7 +208,7 @@
     if (!result.isOk()) {
         return;
     }
-    sp<Aidl::IVibrator> newHandle = result.value();
+    std::shared_ptr<Aidl::IVibrator> newHandle = result.value();
     if (newHandle) {
         std::lock_guard<std::mutex> lock(mHandleMutex);
         mHandle = std::move(newHandle);
@@ -220,7 +220,8 @@
     HalResult<Capabilities> capabilities = getCapabilities();
     bool supportsCallback = capabilities.isOk() &&
             static_cast<int32_t>(capabilities.value() & Capabilities::ON_CALLBACK);
-    auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
+    auto cb = supportsCallback ? ndk::SharedRefBase::make<HalCallbackWrapper>(completionCallback)
+                               : nullptr;
 
     auto ret = HalResultFactory::fromStatus(getHal()->on(timeout.count(), cb));
     if (!supportsCallback && ret.isOk()) {
@@ -255,13 +256,14 @@
     HalResult<Capabilities> capabilities = getCapabilities();
     bool supportsCallback = capabilities.isOk() &&
             static_cast<int32_t>(capabilities.value() & Capabilities::PERFORM_CALLBACK);
-    auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
+    auto cb = supportsCallback ? ndk::SharedRefBase::make<HalCallbackWrapper>(completionCallback)
+                               : nullptr;
 
     int32_t lengthMs;
-    auto result = getHal()->perform(effect, strength, cb, &lengthMs);
+    auto status = getHal()->perform(effect, strength, cb, &lengthMs);
     milliseconds length = milliseconds(lengthMs);
 
-    auto ret = HalResultFactory::fromStatus<milliseconds>(result, length);
+    auto ret = HalResultFactory::fromStatus<milliseconds>(std::move(status), length);
     if (!supportsCallback && ret.isOk()) {
         mCallbackScheduler->schedule(completionCallback, length);
     }
@@ -273,7 +275,7 @@
         const std::vector<CompositeEffect>& primitives,
         const std::function<void()>& completionCallback) {
     // This method should always support callbacks, so no need to double check.
-    auto cb = new HalCallbackWrapper(completionCallback);
+    auto cb = ndk::SharedRefBase::make<HalCallbackWrapper>(completionCallback);
 
     auto durations = getPrimitiveDurations().valueOr({});
     milliseconds duration(0);
@@ -294,40 +296,40 @@
 HalResult<void> AidlHalWrapper::performPwleEffect(const std::vector<PrimitivePwle>& primitives,
                                                   const std::function<void()>& completionCallback) {
     // This method should always support callbacks, so no need to double check.
-    auto cb = new HalCallbackWrapper(completionCallback);
+    auto cb = ndk::SharedRefBase::make<HalCallbackWrapper>(completionCallback);
     return HalResultFactory::fromStatus(getHal()->composePwle(primitives, cb));
 }
 
 HalResult<Capabilities> AidlHalWrapper::getCapabilitiesInternal() {
-    int32_t capabilities = 0;
-    auto result = getHal()->getCapabilities(&capabilities);
-    return HalResultFactory::fromStatus<Capabilities>(result,
-                                                      static_cast<Capabilities>(capabilities));
+    int32_t cap = 0;
+    auto status = getHal()->getCapabilities(&cap);
+    auto capabilities = static_cast<Capabilities>(cap);
+    return HalResultFactory::fromStatus<Capabilities>(std::move(status), capabilities);
 }
 
 HalResult<std::vector<Effect>> AidlHalWrapper::getSupportedEffectsInternal() {
     std::vector<Effect> supportedEffects;
-    auto result = getHal()->getSupportedEffects(&supportedEffects);
-    return HalResultFactory::fromStatus<std::vector<Effect>>(result, supportedEffects);
+    auto status = getHal()->getSupportedEffects(&supportedEffects);
+    return HalResultFactory::fromStatus<std::vector<Effect>>(std::move(status), supportedEffects);
 }
 
 HalResult<std::vector<Braking>> AidlHalWrapper::getSupportedBrakingInternal() {
     std::vector<Braking> supportedBraking;
-    auto result = getHal()->getSupportedBraking(&supportedBraking);
-    return HalResultFactory::fromStatus<std::vector<Braking>>(result, supportedBraking);
+    auto status = getHal()->getSupportedBraking(&supportedBraking);
+    return HalResultFactory::fromStatus<std::vector<Braking>>(std::move(status), supportedBraking);
 }
 
 HalResult<std::vector<CompositePrimitive>> AidlHalWrapper::getSupportedPrimitivesInternal() {
     std::vector<CompositePrimitive> supportedPrimitives;
-    auto result = getHal()->getSupportedPrimitives(&supportedPrimitives);
-    return HalResultFactory::fromStatus<std::vector<CompositePrimitive>>(result,
+    auto status = getHal()->getSupportedPrimitives(&supportedPrimitives);
+    return HalResultFactory::fromStatus<std::vector<CompositePrimitive>>(std::move(status),
                                                                          supportedPrimitives);
 }
 
 HalResult<std::vector<milliseconds>> AidlHalWrapper::getPrimitiveDurationsInternal(
         const std::vector<CompositePrimitive>& supportedPrimitives) {
     std::vector<milliseconds> durations;
-    constexpr auto primitiveRange = enum_range<CompositePrimitive>();
+    constexpr auto primitiveRange = ndk::enum_range<CompositePrimitive>();
     constexpr auto primitiveCount = std::distance(primitiveRange.begin(), primitiveRange.end());
     durations.resize(primitiveCount);
 
@@ -340,8 +342,8 @@
             continue;
         }
         int32_t duration = 0;
-        auto result = getHal()->getPrimitiveDuration(primitive, &duration);
-        auto halResult = HalResultFactory::fromStatus<int32_t>(result, duration);
+        auto status = getHal()->getPrimitiveDuration(primitive, &duration);
+        auto halResult = HalResultFactory::fromStatus<int32_t>(std::move(status), duration);
         if (halResult.isUnsupported()) {
             // Should not happen, supported primitives should always support requesting duration.
             ALOGE("Supported primitive %zu returned unsupported for getPrimitiveDuration",
@@ -349,7 +351,7 @@
         }
         if (halResult.isFailed()) {
             // Fail entire request if one request has failed.
-            return HalResult<std::vector<milliseconds>>::failed(result.toString8().c_str());
+            return HalResult<std::vector<milliseconds>>::failed(status.getMessage());
         }
         durations[primitiveIdx] = milliseconds(duration);
     }
@@ -359,59 +361,59 @@
 
 HalResult<milliseconds> AidlHalWrapper::getPrimitiveDelayMaxInternal() {
     int32_t delay = 0;
-    auto result = getHal()->getCompositionDelayMax(&delay);
-    return HalResultFactory::fromStatus<milliseconds>(result, milliseconds(delay));
+    auto status = getHal()->getCompositionDelayMax(&delay);
+    return HalResultFactory::fromStatus<milliseconds>(std::move(status), milliseconds(delay));
 }
 
 HalResult<milliseconds> AidlHalWrapper::getPrimitiveDurationMaxInternal() {
     int32_t delay = 0;
-    auto result = getHal()->getPwlePrimitiveDurationMax(&delay);
-    return HalResultFactory::fromStatus<milliseconds>(result, milliseconds(delay));
+    auto status = getHal()->getPwlePrimitiveDurationMax(&delay);
+    return HalResultFactory::fromStatus<milliseconds>(std::move(status), milliseconds(delay));
 }
 
 HalResult<int32_t> AidlHalWrapper::getCompositionSizeMaxInternal() {
     int32_t size = 0;
-    auto result = getHal()->getCompositionSizeMax(&size);
-    return HalResultFactory::fromStatus<int32_t>(result, size);
+    auto status = getHal()->getCompositionSizeMax(&size);
+    return HalResultFactory::fromStatus<int32_t>(std::move(status), size);
 }
 
 HalResult<int32_t> AidlHalWrapper::getPwleSizeMaxInternal() {
     int32_t size = 0;
-    auto result = getHal()->getPwleCompositionSizeMax(&size);
-    return HalResultFactory::fromStatus<int32_t>(result, size);
+    auto status = getHal()->getPwleCompositionSizeMax(&size);
+    return HalResultFactory::fromStatus<int32_t>(std::move(status), size);
 }
 
 HalResult<float> AidlHalWrapper::getMinFrequencyInternal() {
     float minFrequency = 0;
-    auto result = getHal()->getFrequencyMinimum(&minFrequency);
-    return HalResultFactory::fromStatus<float>(result, minFrequency);
+    auto status = getHal()->getFrequencyMinimum(&minFrequency);
+    return HalResultFactory::fromStatus<float>(std::move(status), minFrequency);
 }
 
 HalResult<float> AidlHalWrapper::getResonantFrequencyInternal() {
     float f0 = 0;
-    auto result = getHal()->getResonantFrequency(&f0);
-    return HalResultFactory::fromStatus<float>(result, f0);
+    auto status = getHal()->getResonantFrequency(&f0);
+    return HalResultFactory::fromStatus<float>(std::move(status), f0);
 }
 
 HalResult<float> AidlHalWrapper::getFrequencyResolutionInternal() {
     float frequencyResolution = 0;
-    auto result = getHal()->getFrequencyResolution(&frequencyResolution);
-    return HalResultFactory::fromStatus<float>(result, frequencyResolution);
+    auto status = getHal()->getFrequencyResolution(&frequencyResolution);
+    return HalResultFactory::fromStatus<float>(std::move(status), frequencyResolution);
 }
 
 HalResult<float> AidlHalWrapper::getQFactorInternal() {
     float qFactor = 0;
-    auto result = getHal()->getQFactor(&qFactor);
-    return HalResultFactory::fromStatus<float>(result, qFactor);
+    auto status = getHal()->getQFactor(&qFactor);
+    return HalResultFactory::fromStatus<float>(std::move(status), qFactor);
 }
 
 HalResult<std::vector<float>> AidlHalWrapper::getMaxAmplitudesInternal() {
     std::vector<float> amplitudes;
-    auto result = getHal()->getBandwidthAmplitudeMap(&amplitudes);
-    return HalResultFactory::fromStatus<std::vector<float>>(result, amplitudes);
+    auto status = getHal()->getBandwidthAmplitudeMap(&amplitudes);
+    return HalResultFactory::fromStatus<std::vector<float>>(std::move(status), amplitudes);
 }
 
-sp<Aidl::IVibrator> AidlHalWrapper::getHal() {
+std::shared_ptr<Aidl::IVibrator> AidlHalWrapper::getHal() {
     std::lock_guard<std::mutex> lock(mHandleMutex);
     return mHandle;
 }
@@ -420,8 +422,7 @@
 
 template <typename I>
 HalResult<void> HidlHalWrapper<I>::ping() {
-    auto result = getHal()->ping();
-    return HalResultFactory::fromReturn(result);
+    return HalResultFactory::fromReturn(getHal()->ping());
 }
 
 template <typename I>
@@ -436,8 +437,8 @@
 template <typename I>
 HalResult<void> HidlHalWrapper<I>::on(milliseconds timeout,
                                       const std::function<void()>& completionCallback) {
-    auto result = getHal()->on(timeout.count());
-    auto ret = HalResultFactory::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+    auto status = getHal()->on(timeout.count());
+    auto ret = HalResultFactory::fromStatus(status.withDefault(V1_0::Status::UNKNOWN_ERROR));
     if (ret.isOk()) {
         mCallbackScheduler->schedule(completionCallback, timeout);
     }
@@ -446,15 +447,15 @@
 
 template <typename I>
 HalResult<void> HidlHalWrapper<I>::off() {
-    auto result = getHal()->off();
-    return HalResultFactory::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+    auto status = getHal()->off();
+    return HalResultFactory::fromStatus(status.withDefault(V1_0::Status::UNKNOWN_ERROR));
 }
 
 template <typename I>
 HalResult<void> HidlHalWrapper<I>::setAmplitude(float amplitude) {
     uint8_t amp = static_cast<uint8_t>(amplitude * std::numeric_limits<uint8_t>::max());
-    auto result = getHal()->setAmplitude(amp);
-    return HalResultFactory::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+    auto status = getHal()->setAmplitude(amp);
+    return HalResultFactory::fromStatus(status.withDefault(V1_0::Status::UNKNOWN_ERROR));
 }
 
 template <typename I>
@@ -480,7 +481,7 @@
     hardware::Return<bool> result = getHal()->supportsAmplitudeControl();
     Capabilities capabilities =
             result.withDefault(false) ? Capabilities::AMPLITUDE_CONTROL : Capabilities::NONE;
-    return HalResultFactory::fromReturn<Capabilities>(result, capabilities);
+    return HalResultFactory::fromReturn<Capabilities>(std::move(result), capabilities);
 }
 
 template <typename I>
@@ -499,7 +500,7 @@
     auto result = std::invoke(performFn, handle, effect, effectStrength, effectCallback);
     milliseconds length = milliseconds(lengthMs);
 
-    auto ret = HalResultFactory::fromReturn<milliseconds>(result, status, length);
+    auto ret = HalResultFactory::fromReturn<milliseconds>(std::move(result), status, length);
     if (ret.isOk()) {
         mCallbackScheduler->schedule(completionCallback, length);
     }
@@ -604,7 +605,7 @@
     sp<V1_3::IVibrator> hal = getHal();
     auto amplitudeResult = hal->supportsAmplitudeControl();
     if (!amplitudeResult.isOk()) {
-        return HalResultFactory::fromReturn<Capabilities>(amplitudeResult, capabilities);
+        return HalResultFactory::fromReturn<Capabilities>(std::move(amplitudeResult), capabilities);
     }
 
     auto externalControlResult = hal->supportsExternalControl();
@@ -619,7 +620,8 @@
         }
     }
 
-    return HalResultFactory::fromReturn<Capabilities>(externalControlResult, capabilities);
+    return HalResultFactory::fromReturn<Capabilities>(std::move(externalControlResult),
+                                                      capabilities);
 }
 
 // -------------------------------------------------------------------------------------------------
diff --git a/services/vibratorservice/VibratorManagerHalController.cpp b/services/vibratorservice/VibratorManagerHalController.cpp
index aa5b7fc..ba35d15 100644
--- a/services/vibratorservice/VibratorManagerHalController.cpp
+++ b/services/vibratorservice/VibratorManagerHalController.cpp
@@ -20,7 +20,7 @@
 
 #include <vibratorservice/VibratorManagerHalController.h>
 
-namespace Aidl = android::hardware::vibrator;
+namespace Aidl = aidl::android::hardware::vibrator;
 
 namespace android {
 
@@ -29,10 +29,15 @@
 std::shared_ptr<ManagerHalWrapper> connectManagerHal(std::shared_ptr<CallbackScheduler> scheduler) {
     static bool gHalExists = true;
     if (gHalExists) {
-        sp<Aidl::IVibratorManager> hal = waitForVintfService<Aidl::IVibratorManager>();
-        if (hal) {
-            ALOGV("Successfully connected to VibratorManager HAL AIDL service.");
-            return std::make_shared<AidlManagerHalWrapper>(std::move(scheduler), hal);
+        auto serviceName = std::string(Aidl::IVibratorManager::descriptor) + "/default";
+        if (AServiceManager_isDeclared(serviceName.c_str())) {
+            std::shared_ptr<Aidl::IVibratorManager> hal = Aidl::IVibratorManager::fromBinder(
+                    ndk::SpAIBinder(AServiceManager_checkService(serviceName.c_str())));
+            if (hal) {
+                ALOGV("Successfully connected to VibratorManager HAL AIDL service.");
+                return std::make_shared<AidlManagerHalWrapper>(std::move(scheduler),
+                                                               std::move(hal));
+            }
         }
     }
 
diff --git a/services/vibratorservice/VibratorManagerHalWrapper.cpp b/services/vibratorservice/VibratorManagerHalWrapper.cpp
index 1341266..93ec781 100644
--- a/services/vibratorservice/VibratorManagerHalWrapper.cpp
+++ b/services/vibratorservice/VibratorManagerHalWrapper.cpp
@@ -20,7 +20,7 @@
 
 #include <vibratorservice/VibratorManagerHalWrapper.h>
 
-namespace Aidl = android::hardware::vibrator;
+namespace Aidl = aidl::android::hardware::vibrator;
 
 namespace android {
 
@@ -75,10 +75,11 @@
 
 std::shared_ptr<HalWrapper> AidlManagerHalWrapper::connectToVibrator(
         int32_t vibratorId, std::shared_ptr<CallbackScheduler> callbackScheduler) {
-    std::function<HalResult<sp<Aidl::IVibrator>>()> reconnectFn = [=, this]() {
-        sp<Aidl::IVibrator> vibrator;
-        auto result = this->getHal()->getVibrator(vibratorId, &vibrator);
-        return HalResultFactory::fromStatus<sp<Aidl::IVibrator>>(result, vibrator);
+    std::function<HalResult<std::shared_ptr<Aidl::IVibrator>>()> reconnectFn = [=, this]() {
+        std::shared_ptr<Aidl::IVibrator> vibrator;
+        auto status = this->getHal()->getVibrator(vibratorId, &vibrator);
+        return HalResultFactory::fromStatus<std::shared_ptr<Aidl::IVibrator>>(std::move(status),
+                                                                              vibrator);
     };
     auto result = reconnectFn();
     if (!result.isOk()) {
@@ -93,11 +94,13 @@
 }
 
 HalResult<void> AidlManagerHalWrapper::ping() {
-    return HalResultFactory::fromStatus(IInterface::asBinder(getHal())->pingBinder());
+    return HalResultFactory::fromStatus(AIBinder_ping(getHal()->asBinder().get()));
 }
 
 void AidlManagerHalWrapper::tryReconnect() {
-    sp<Aidl::IVibratorManager> newHandle = checkVintfService<Aidl::IVibratorManager>();
+    auto aidlServiceName = std::string(Aidl::IVibratorManager::descriptor) + "/default";
+    std::shared_ptr<Aidl::IVibratorManager> newHandle = Aidl::IVibratorManager::fromBinder(
+            ndk::SpAIBinder(AServiceManager_checkService(aidlServiceName.c_str())));
     if (newHandle) {
         std::lock_guard<std::mutex> lock(mHandleMutex);
         mHandle = std::move(newHandle);
@@ -111,9 +114,9 @@
         return HalResult<ManagerCapabilities>::ok(*mCapabilities);
     }
     int32_t cap = 0;
-    auto result = getHal()->getCapabilities(&cap);
+    auto status = getHal()->getCapabilities(&cap);
     auto capabilities = static_cast<ManagerCapabilities>(cap);
-    auto ret = HalResultFactory::fromStatus<ManagerCapabilities>(result, capabilities);
+    auto ret = HalResultFactory::fromStatus<ManagerCapabilities>(std::move(status), capabilities);
     if (ret.isOk()) {
         // Cache copy of returned value.
         mCapabilities.emplace(ret.value());
@@ -128,8 +131,8 @@
         return HalResult<std::vector<int32_t>>::ok(*mVibratorIds);
     }
     std::vector<int32_t> ids;
-    auto result = getHal()->getVibratorIds(&ids);
-    auto ret = HalResultFactory::fromStatus<std::vector<int32_t>>(result, ids);
+    auto status = getHal()->getVibratorIds(&ids);
+    auto ret = HalResultFactory::fromStatus<std::vector<int32_t>>(std::move(status), ids);
     if (ret.isOk()) {
         // Cache copy of returned value and the individual controllers.
         mVibratorIds.emplace(ret.value());
@@ -178,7 +181,8 @@
     HalResult<ManagerCapabilities> capabilities = getCapabilities();
     bool supportsCallback = capabilities.isOk() &&
             static_cast<int32_t>(capabilities.value() & ManagerCapabilities::TRIGGER_CALLBACK);
-    auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
+    auto cb = supportsCallback ? ndk::SharedRefBase::make<HalCallbackWrapper>(completionCallback)
+                               : nullptr;
     return HalResultFactory::fromStatus(getHal()->triggerSynced(cb));
 }
 
@@ -196,7 +200,7 @@
     return ret;
 }
 
-sp<Aidl::IVibratorManager> AidlManagerHalWrapper::getHal() {
+std::shared_ptr<Aidl::IVibratorManager> AidlManagerHalWrapper::getHal() {
     std::lock_guard<std::mutex> lock(mHandleMutex);
     return mHandle;
 }
diff --git a/services/vibratorservice/benchmarks/Android.bp b/services/vibratorservice/benchmarks/Android.bp
index 5437995..5bb8ceb 100644
--- a/services/vibratorservice/benchmarks/Android.bp
+++ b/services/vibratorservice/benchmarks/Android.bp
@@ -28,12 +28,12 @@
         "VibratorHalControllerBenchmarks.cpp",
     ],
     shared_libs: [
-        "libbinder",
+        "libbinder_ndk",
         "libhidlbase",
         "liblog",
         "libutils",
         "libvibratorservice",
-        "android.hardware.vibrator-V2-cpp",
+        "android.hardware.vibrator-V2-ndk",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
diff --git a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
index 9b30337..5c7c9f4 100644
--- a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
+++ b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
@@ -16,16 +16,15 @@
 
 #define LOG_TAG "VibratorHalControllerBenchmarks"
 
+#include <android/binder_process.h>
 #include <benchmark/benchmark.h>
-#include <binder/ProcessState.h>
 #include <vibratorservice/VibratorHalController.h>
 #include <future>
 
-using ::android::enum_range;
-using ::android::hardware::vibrator::CompositeEffect;
-using ::android::hardware::vibrator::CompositePrimitive;
-using ::android::hardware::vibrator::Effect;
-using ::android::hardware::vibrator::EffectStrength;
+using ::aidl::android::hardware::vibrator::CompositeEffect;
+using ::aidl::android::hardware::vibrator::CompositePrimitive;
+using ::aidl::android::hardware::vibrator::Effect;
+using ::aidl::android::hardware::vibrator::EffectStrength;
 using ::benchmark::Counter;
 using ::benchmark::Fixture;
 using ::benchmark::kMicrosecond;
@@ -115,8 +114,8 @@
 class VibratorBench : public Fixture {
 public:
     void SetUp(State& /*state*/) override {
-        android::ProcessState::self()->setThreadPoolMaxThreadCount(1);
-        android::ProcessState::self()->startThreadPool();
+        ABinderProcess_setThreadPoolMaxThreadCount(1);
+        ABinderProcess_startThreadPool();
         mController.init();
     }
 
@@ -388,11 +387,11 @@
             return;
         }
 
-        for (const auto& effect : enum_range<Effect>()) {
+        for (const auto& effect : ndk::enum_range<Effect>()) {
             if (std::find(supported.begin(), supported.end(), effect) == supported.end()) {
                 continue;
             }
-            for (const auto& strength : enum_range<EffectStrength>()) {
+            for (const auto& strength : ndk::enum_range<EffectStrength>()) {
                 b->Args({static_cast<long>(effect), static_cast<long>(strength)});
             }
         }
@@ -533,7 +532,7 @@
             return;
         }
 
-        for (const auto& primitive : enum_range<CompositePrimitive>()) {
+        for (const auto& primitive : ndk::enum_range<CompositePrimitive>()) {
             if (std::find(supported.begin(), supported.end(), primitive) == supported.end()) {
                 continue;
             }
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalController.h b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
index f97442d..a1cb3fa 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
@@ -17,8 +17,8 @@
 #ifndef ANDROID_OS_VIBRATORHALCONTROLLER_H
 #define ANDROID_OS_VIBRATORHALCONTROLLER_H
 
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 #include <android-base/thread_annotations.h>
-#include <android/hardware/vibrator/IVibrator.h>
 
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index 39c4eb4..d4f7f1d 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -17,10 +17,12 @@
 #ifndef ANDROID_OS_VIBRATORHALWRAPPER_H
 #define ANDROID_OS_VIBRATORHALWRAPPER_H
 
+#include <aidl/android/hardware/vibrator/BnVibratorCallback.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+
 #include <android-base/thread_annotations.h>
+#include <android/binder_manager.h>
 #include <android/hardware/vibrator/1.3/IVibrator.h>
-#include <android/hardware/vibrator/BnVibratorCallback.h>
-#include <android/hardware/vibrator/IVibrator.h>
 #include <binder/IServiceManager.h>
 
 #include <vibratorservice/VibratorCallbackScheduler.h>
@@ -98,43 +100,49 @@
 class HalResultFactory {
 public:
     template <typename T>
-    static HalResult<T> fromStatus(binder::Status status, T data) {
-        return status.isOk() ? HalResult<T>::ok(data) : fromFailedStatus<T>(status);
+    static HalResult<T> fromStatus(ndk::ScopedAStatus&& status, T data) {
+        return status.isOk() ? HalResult<T>::ok(std::move(data))
+                             : fromFailedStatus<T>(std::move(status));
     }
 
     template <typename T>
-    static HalResult<T> fromStatus(hardware::vibrator::V1_0::Status status, T data) {
-        return (status == hardware::vibrator::V1_0::Status::OK) ? HalResult<T>::ok(data)
-                                                                : fromFailedStatus<T>(status);
+    static HalResult<T> fromStatus(hardware::vibrator::V1_0::Status&& status, T data) {
+        return (status == hardware::vibrator::V1_0::Status::OK)
+                ? HalResult<T>::ok(std::move(data))
+                : fromFailedStatus<T>(std::move(status));
     }
 
     template <typename T, typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, T data) {
-        return ret.isOk() ? HalResult<T>::ok(data) : fromFailedReturn<T, R>(ret);
+    static HalResult<T> fromReturn(hardware::Return<R>&& ret, T data) {
+        return ret.isOk() ? HalResult<T>::ok(std::move(data))
+                          : fromFailedReturn<T, R>(std::move(ret));
     }
 
     template <typename T, typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret,
+    static HalResult<T> fromReturn(hardware::Return<R>&& ret,
                                    hardware::vibrator::V1_0::Status status, T data) {
-        return ret.isOk() ? fromStatus<T>(status, data) : fromFailedReturn<T, R>(ret);
+        return ret.isOk() ? fromStatus<T>(std::move(status), std::move(data))
+                          : fromFailedReturn<T, R>(std::move(ret));
     }
 
     static HalResult<void> fromStatus(status_t status) {
-        return (status == android::OK) ? HalResult<void>::ok() : fromFailedStatus<void>(status);
+        return (status == android::OK) ? HalResult<void>::ok()
+                                       : fromFailedStatus<void>(std::move(status));
     }
 
-    static HalResult<void> fromStatus(binder::Status status) {
-        return status.isOk() ? HalResult<void>::ok() : fromFailedStatus<void>(status);
+    static HalResult<void> fromStatus(ndk::ScopedAStatus&& status) {
+        return status.isOk() ? HalResult<void>::ok() : fromFailedStatus<void>(std::move(status));
     }
 
-    static HalResult<void> fromStatus(hardware::vibrator::V1_0::Status status) {
-        return (status == hardware::vibrator::V1_0::Status::OK) ? HalResult<void>::ok()
-                                                                : fromFailedStatus<void>(status);
+    static HalResult<void> fromStatus(hardware::vibrator::V1_0::Status&& status) {
+        return (status == hardware::vibrator::V1_0::Status::OK)
+                ? HalResult<void>::ok()
+                : fromFailedStatus<void>(std::move(status));
     }
 
     template <typename R>
-    static HalResult<void> fromReturn(hardware::Return<R>& ret) {
-        return ret.isOk() ? HalResult<void>::ok() : fromFailedReturn<void, R>(ret);
+    static HalResult<void> fromReturn(hardware::Return<R>&& ret) {
+        return ret.isOk() ? HalResult<void>::ok() : fromFailedReturn<void, R>(std::move(ret));
     }
 
 private:
@@ -146,21 +154,21 @@
     }
 
     template <typename T>
-    static HalResult<T> fromFailedStatus(binder::Status status) {
-        if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION ||
-            status.transactionError() == android::UNKNOWN_TRANSACTION) {
-            // UNKNOWN_TRANSACTION means the HAL implementation is an older version, so this is
-            // the same as the operation being unsupported by this HAL. Should not retry.
+    static HalResult<T> fromFailedStatus(ndk::ScopedAStatus&& status) {
+        if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION ||
+            status.getStatus() == STATUS_UNKNOWN_TRANSACTION) {
+            // STATUS_UNKNOWN_TRANSACTION means the HAL implementation is an older version, so this
+            // is the same as the operation being unsupported by this HAL. Should not retry.
             return HalResult<T>::unsupported();
         }
-        if (status.exceptionCode() == binder::Status::EX_TRANSACTION_FAILED) {
-            return HalResult<T>::transactionFailed(status.toString8().c_str());
+        if (status.getExceptionCode() == EX_TRANSACTION_FAILED) {
+            return HalResult<T>::transactionFailed(status.getMessage());
         }
-        return HalResult<T>::failed(status.toString8().c_str());
+        return HalResult<T>::failed(status.getMessage());
     }
 
     template <typename T>
-    static HalResult<T> fromFailedStatus(hardware::vibrator::V1_0::Status status) {
+    static HalResult<T> fromFailedStatus(hardware::vibrator::V1_0::Status&& status) {
         switch (status) {
             case hardware::vibrator::V1_0::Status::UNSUPPORTED_OPERATION:
                 return HalResult<T>::unsupported();
@@ -171,7 +179,7 @@
     }
 
     template <typename T, typename R>
-    static HalResult<T> fromFailedReturn(hardware::Return<R>& ret) {
+    static HalResult<T> fromFailedReturn(hardware::Return<R>&& ret) {
         return ret.isDeadObject() ? HalResult<T>::transactionFailed(ret.description().c_str())
                                   : HalResult<T>::failed(ret.description().c_str());
     }
@@ -179,14 +187,14 @@
 
 // -------------------------------------------------------------------------------------------------
 
-class HalCallbackWrapper : public hardware::vibrator::BnVibratorCallback {
+class HalCallbackWrapper : public aidl::android::hardware::vibrator::BnVibratorCallback {
 public:
     HalCallbackWrapper(std::function<void()> completionCallback)
           : mCompletionCallback(completionCallback) {}
 
-    binder::Status onComplete() override {
+    ndk::ScopedAStatus onComplete() override {
         mCompletionCallback();
-        return binder::Status::ok();
+        return ndk::ScopedAStatus::ok();
     }
 
 private:
@@ -198,14 +206,15 @@
 // Vibrator HAL capabilities.
 enum class Capabilities : int32_t {
     NONE = 0,
-    ON_CALLBACK = hardware::vibrator::IVibrator::CAP_ON_CALLBACK,
-    PERFORM_CALLBACK = hardware::vibrator::IVibrator::CAP_PERFORM_CALLBACK,
-    AMPLITUDE_CONTROL = hardware::vibrator::IVibrator::CAP_AMPLITUDE_CONTROL,
-    EXTERNAL_CONTROL = hardware::vibrator::IVibrator::CAP_EXTERNAL_CONTROL,
-    EXTERNAL_AMPLITUDE_CONTROL = hardware::vibrator::IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL,
-    COMPOSE_EFFECTS = hardware::vibrator::IVibrator::CAP_COMPOSE_EFFECTS,
-    COMPOSE_PWLE_EFFECTS = hardware::vibrator::IVibrator::CAP_COMPOSE_PWLE_EFFECTS,
-    ALWAYS_ON_CONTROL = hardware::vibrator::IVibrator::CAP_ALWAYS_ON_CONTROL,
+    ON_CALLBACK = aidl::android::hardware::vibrator::IVibrator::CAP_ON_CALLBACK,
+    PERFORM_CALLBACK = aidl::android::hardware::vibrator::IVibrator::CAP_PERFORM_CALLBACK,
+    AMPLITUDE_CONTROL = aidl::android::hardware::vibrator::IVibrator::CAP_AMPLITUDE_CONTROL,
+    EXTERNAL_CONTROL = aidl::android::hardware::vibrator::IVibrator::CAP_EXTERNAL_CONTROL,
+    EXTERNAL_AMPLITUDE_CONTROL =
+            aidl::android::hardware::vibrator::IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL,
+    COMPOSE_EFFECTS = aidl::android::hardware::vibrator::IVibrator::CAP_COMPOSE_EFFECTS,
+    COMPOSE_PWLE_EFFECTS = aidl::android::hardware::vibrator::IVibrator::CAP_COMPOSE_PWLE_EFFECTS,
+    ALWAYS_ON_CONTROL = aidl::android::hardware::vibrator::IVibrator::CAP_ALWAYS_ON_CONTROL,
 };
 
 inline Capabilities operator|(Capabilities lhs, Capabilities rhs) {
@@ -230,10 +239,15 @@
 
 class Info {
 public:
+    using Effect = aidl::android::hardware::vibrator::Effect;
+    using EffectStrength = aidl::android::hardware::vibrator::EffectStrength;
+    using CompositePrimitive = aidl::android::hardware::vibrator::CompositePrimitive;
+    using Braking = aidl::android::hardware::vibrator::Braking;
+
     const HalResult<Capabilities> capabilities;
-    const HalResult<std::vector<hardware::vibrator::Effect>> supportedEffects;
-    const HalResult<std::vector<hardware::vibrator::Braking>> supportedBraking;
-    const HalResult<std::vector<hardware::vibrator::CompositePrimitive>> supportedPrimitives;
+    const HalResult<std::vector<Effect>> supportedEffects;
+    const HalResult<std::vector<Braking>> supportedBraking;
+    const HalResult<std::vector<CompositePrimitive>> supportedPrimitives;
     const HalResult<std::vector<std::chrono::milliseconds>> primitiveDurations;
     const HalResult<std::chrono::milliseconds> primitiveDelayMax;
     const HalResult<std::chrono::milliseconds> pwlePrimitiveDurationMax;
@@ -247,12 +261,9 @@
 
     void logFailures() const {
         logFailure<Capabilities>(capabilities, "getCapabilities");
-        logFailure<std::vector<hardware::vibrator::Effect>>(supportedEffects,
-                                                            "getSupportedEffects");
-        logFailure<std::vector<hardware::vibrator::Braking>>(supportedBraking,
-                                                             "getSupportedBraking");
-        logFailure<std::vector<hardware::vibrator::CompositePrimitive>>(supportedPrimitives,
-                                                                        "getSupportedPrimitives");
+        logFailure<std::vector<Effect>>(supportedEffects, "getSupportedEffects");
+        logFailure<std::vector<Braking>>(supportedBraking, "getSupportedBraking");
+        logFailure<std::vector<CompositePrimitive>>(supportedPrimitives, "getSupportedPrimitives");
         logFailure<std::vector<std::chrono::milliseconds>>(primitiveDurations,
                                                            "getPrimitiveDuration");
         logFailure<std::chrono::milliseconds>(primitiveDelayMax, "getPrimitiveDelayMax");
@@ -309,12 +320,12 @@
     // Create a transaction failed results as default so we can retry on the first time we get them.
     static const constexpr char* MSG = "never loaded";
     HalResult<Capabilities> mCapabilities = HalResult<Capabilities>::transactionFailed(MSG);
-    HalResult<std::vector<hardware::vibrator::Effect>> mSupportedEffects =
-            HalResult<std::vector<hardware::vibrator::Effect>>::transactionFailed(MSG);
-    HalResult<std::vector<hardware::vibrator::Braking>> mSupportedBraking =
-            HalResult<std::vector<hardware::vibrator::Braking>>::transactionFailed(MSG);
-    HalResult<std::vector<hardware::vibrator::CompositePrimitive>> mSupportedPrimitives =
-            HalResult<std::vector<hardware::vibrator::CompositePrimitive>>::transactionFailed(MSG);
+    HalResult<std::vector<Info::Effect>> mSupportedEffects =
+            HalResult<std::vector<Info::Effect>>::transactionFailed(MSG);
+    HalResult<std::vector<Info::Braking>> mSupportedBraking =
+            HalResult<std::vector<Info::Braking>>::transactionFailed(MSG);
+    HalResult<std::vector<Info::CompositePrimitive>> mSupportedPrimitives =
+            HalResult<std::vector<Info::CompositePrimitive>>::transactionFailed(MSG);
     HalResult<std::vector<std::chrono::milliseconds>> mPrimitiveDurations =
             HalResult<std::vector<std::chrono::milliseconds>>::transactionFailed(MSG);
     HalResult<std::chrono::milliseconds> mPrimitiveDelayMax =
@@ -336,6 +347,13 @@
 // Wrapper for Vibrator HAL handlers.
 class HalWrapper {
 public:
+    using Effect = aidl::android::hardware::vibrator::Effect;
+    using EffectStrength = aidl::android::hardware::vibrator::EffectStrength;
+    using CompositePrimitive = aidl::android::hardware::vibrator::CompositePrimitive;
+    using CompositeEffect = aidl::android::hardware::vibrator::CompositeEffect;
+    using Braking = aidl::android::hardware::vibrator::Braking;
+    using PrimitivePwle = aidl::android::hardware::vibrator::PrimitivePwle;
+
     explicit HalWrapper(std::shared_ptr<CallbackScheduler> scheduler)
           : mCallbackScheduler(std::move(scheduler)) {}
     virtual ~HalWrapper() = default;
@@ -355,21 +373,19 @@
     virtual HalResult<void> setAmplitude(float amplitude) = 0;
     virtual HalResult<void> setExternalControl(bool enabled) = 0;
 
-    virtual HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
-                                           hardware::vibrator::EffectStrength strength) = 0;
+    virtual HalResult<void> alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) = 0;
     virtual HalResult<void> alwaysOnDisable(int32_t id) = 0;
 
     virtual HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            Effect effect, EffectStrength strength,
             const std::function<void()>& completionCallback) = 0;
 
     virtual HalResult<std::chrono::milliseconds> performComposedEffect(
-            const std::vector<hardware::vibrator::CompositeEffect>& primitives,
+            const std::vector<CompositeEffect>& primitives,
             const std::function<void()>& completionCallback);
 
-    virtual HalResult<void> performPwleEffect(
-            const std::vector<hardware::vibrator::PrimitivePwle>& primitives,
-            const std::function<void()>& completionCallback);
+    virtual HalResult<void> performPwleEffect(const std::vector<PrimitivePwle>& primitives,
+                                              const std::function<void()>& completionCallback);
 
 protected:
     // Shared pointer to allow CallbackScheduler to outlive this wrapper.
@@ -381,12 +397,11 @@
 
     // Request vibrator info to HAL skipping cache.
     virtual HalResult<Capabilities> getCapabilitiesInternal() = 0;
-    virtual HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffectsInternal();
-    virtual HalResult<std::vector<hardware::vibrator::Braking>> getSupportedBrakingInternal();
-    virtual HalResult<std::vector<hardware::vibrator::CompositePrimitive>>
-    getSupportedPrimitivesInternal();
+    virtual HalResult<std::vector<Effect>> getSupportedEffectsInternal();
+    virtual HalResult<std::vector<Braking>> getSupportedBrakingInternal();
+    virtual HalResult<std::vector<CompositePrimitive>> getSupportedPrimitivesInternal();
     virtual HalResult<std::vector<std::chrono::milliseconds>> getPrimitiveDurationsInternal(
-            const std::vector<hardware::vibrator::CompositePrimitive>& supportedPrimitives);
+            const std::vector<CompositePrimitive>& supportedPrimitives);
     virtual HalResult<std::chrono::milliseconds> getPrimitiveDelayMaxInternal();
     virtual HalResult<std::chrono::milliseconds> getPrimitiveDurationMaxInternal();
     virtual HalResult<int32_t> getCompositionSizeMaxInternal();
@@ -405,12 +420,17 @@
 // Wrapper for the AIDL Vibrator HAL.
 class AidlHalWrapper : public HalWrapper {
 public:
+    using IVibrator = aidl::android::hardware::vibrator::IVibrator;
+    using reconnect_fn = std::function<HalResult<std::shared_ptr<IVibrator>>()>;
+
     AidlHalWrapper(
-            std::shared_ptr<CallbackScheduler> scheduler, sp<hardware::vibrator::IVibrator> handle,
-            std::function<HalResult<sp<hardware::vibrator::IVibrator>>()> reconnectFn =
+            std::shared_ptr<CallbackScheduler> scheduler, std::shared_ptr<IVibrator> handle,
+            reconnect_fn reconnectFn =
                     []() {
-                        return HalResult<sp<hardware::vibrator::IVibrator>>::ok(
-                                checkVintfService<hardware::vibrator::IVibrator>());
+                        auto serviceName = std::string(IVibrator::descriptor) + "/default";
+                        auto hal = IVibrator::fromBinder(
+                                ndk::SpAIBinder(AServiceManager_checkService(serviceName.c_str())));
+                        return HalResult<std::shared_ptr<IVibrator>>::ok(std::move(hal));
                     })
           : HalWrapper(std::move(scheduler)),
             mReconnectFn(reconnectFn),
@@ -427,32 +447,29 @@
     HalResult<void> setAmplitude(float amplitude) override final;
     HalResult<void> setExternalControl(bool enabled) override final;
 
-    HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
-                                   hardware::vibrator::EffectStrength strength) override final;
+    HalResult<void> alwaysOnEnable(int32_t id, Effect effect,
+                                   EffectStrength strength) override final;
     HalResult<void> alwaysOnDisable(int32_t id) override final;
 
     HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            Effect effect, EffectStrength strength,
             const std::function<void()>& completionCallback) override final;
 
     HalResult<std::chrono::milliseconds> performComposedEffect(
-            const std::vector<hardware::vibrator::CompositeEffect>& primitives,
+            const std::vector<CompositeEffect>& primitives,
             const std::function<void()>& completionCallback) override final;
 
     HalResult<void> performPwleEffect(
-            const std::vector<hardware::vibrator::PrimitivePwle>& primitives,
+            const std::vector<PrimitivePwle>& primitives,
             const std::function<void()>& completionCallback) override final;
 
 protected:
     HalResult<Capabilities> getCapabilitiesInternal() override final;
-    HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffectsInternal() override final;
-    HalResult<std::vector<hardware::vibrator::Braking>> getSupportedBrakingInternal()
-            override final;
-    HalResult<std::vector<hardware::vibrator::CompositePrimitive>> getSupportedPrimitivesInternal()
-            override final;
+    HalResult<std::vector<Effect>> getSupportedEffectsInternal() override final;
+    HalResult<std::vector<Braking>> getSupportedBrakingInternal() override final;
+    HalResult<std::vector<CompositePrimitive>> getSupportedPrimitivesInternal() override final;
     HalResult<std::vector<std::chrono::milliseconds>> getPrimitiveDurationsInternal(
-            const std::vector<hardware::vibrator::CompositePrimitive>& supportedPrimitives)
-            override final;
+            const std::vector<CompositePrimitive>& supportedPrimitives) override final;
     HalResult<std::chrono::milliseconds> getPrimitiveDelayMaxInternal() override final;
     HalResult<std::chrono::milliseconds> getPrimitiveDurationMaxInternal() override final;
     HalResult<int32_t> getCompositionSizeMaxInternal() override final;
@@ -464,11 +481,11 @@
     HalResult<std::vector<float>> getMaxAmplitudesInternal() override final;
 
 private:
-    const std::function<HalResult<sp<hardware::vibrator::IVibrator>>()> mReconnectFn;
+    const reconnect_fn mReconnectFn;
     std::mutex mHandleMutex;
-    sp<hardware::vibrator::IVibrator> mHandle GUARDED_BY(mHandleMutex);
+    std::shared_ptr<IVibrator> mHandle GUARDED_BY(mHandleMutex);
 
-    sp<hardware::vibrator::IVibrator> getHal();
+    std::shared_ptr<IVibrator> getHal();
 };
 
 // Wrapper for the HDIL Vibrator HALs.
@@ -489,8 +506,8 @@
     HalResult<void> setAmplitude(float amplitude) override final;
     virtual HalResult<void> setExternalControl(bool enabled) override;
 
-    HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
-                                   hardware::vibrator::EffectStrength strength) override final;
+    HalResult<void> alwaysOnEnable(int32_t id, HalWrapper::Effect effect,
+                                   HalWrapper::EffectStrength strength) override final;
     HalResult<void> alwaysOnDisable(int32_t id) override final;
 
 protected:
@@ -506,8 +523,7 @@
 
     template <class T>
     HalResult<std::chrono::milliseconds> performInternal(
-            perform_fn<T> performFn, sp<I> handle, T effect,
-            hardware::vibrator::EffectStrength strength,
+            perform_fn<T> performFn, sp<I> handle, T effect, HalWrapper::EffectStrength strength,
             const std::function<void()>& completionCallback);
 
     sp<I> getHal();
@@ -523,7 +539,7 @@
     virtual ~HidlHalWrapperV1_0() = default;
 
     HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            HalWrapper::Effect effect, HalWrapper::EffectStrength strength,
             const std::function<void()>& completionCallback) override final;
 };
 
@@ -537,7 +553,7 @@
     virtual ~HidlHalWrapperV1_1() = default;
 
     HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            HalWrapper::Effect effect, HalWrapper::EffectStrength strength,
             const std::function<void()>& completionCallback) override final;
 };
 
@@ -551,7 +567,7 @@
     virtual ~HidlHalWrapperV1_2() = default;
 
     HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            HalWrapper::Effect effect, HalWrapper::EffectStrength strength,
             const std::function<void()>& completionCallback) override final;
 };
 
@@ -567,7 +583,7 @@
     HalResult<void> setExternalControl(bool enabled) override final;
 
     HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            HalWrapper::Effect effect, HalWrapper::EffectStrength strength,
             const std::function<void()>& completionCallback) override final;
 
 protected:
diff --git a/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h b/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
index 9168565..70c846b 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
@@ -17,7 +17,7 @@
 #ifndef ANDROID_OS_VIBRATOR_MANAGER_HAL_CONTROLLER_H
 #define ANDROID_OS_VIBRATOR_MANAGER_HAL_CONTROLLER_H
 
-#include <android/hardware/vibrator/IVibratorManager.h>
+#include <aidl/android/hardware/vibrator/IVibratorManager.h>
 #include <vibratorservice/VibratorHalController.h>
 #include <vibratorservice/VibratorManagerHalWrapper.h>
 #include <unordered_map>
diff --git a/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h
index 563f55e..9e3f221 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h
@@ -17,7 +17,7 @@
 #ifndef ANDROID_OS_VIBRATOR_MANAGER_HAL_WRAPPER_H
 #define ANDROID_OS_VIBRATOR_MANAGER_HAL_WRAPPER_H
 
-#include <android/hardware/vibrator/IVibratorManager.h>
+#include <aidl/android/hardware/vibrator/IVibratorManager.h>
 #include <vibratorservice/VibratorHalController.h>
 #include <unordered_map>
 
@@ -28,14 +28,17 @@
 // VibratorManager HAL capabilities.
 enum class ManagerCapabilities : int32_t {
     NONE = 0,
-    SYNC = hardware::vibrator::IVibratorManager::CAP_SYNC,
-    PREPARE_ON = hardware::vibrator::IVibratorManager::CAP_PREPARE_ON,
-    PREPARE_PERFORM = hardware::vibrator::IVibratorManager::CAP_PREPARE_PERFORM,
-    PREPARE_COMPOSE = hardware::vibrator::IVibratorManager::CAP_PREPARE_COMPOSE,
-    MIXED_TRIGGER_ON = hardware::vibrator::IVibratorManager::IVibratorManager::CAP_MIXED_TRIGGER_ON,
-    MIXED_TRIGGER_PERFORM = hardware::vibrator::IVibratorManager::CAP_MIXED_TRIGGER_PERFORM,
-    MIXED_TRIGGER_COMPOSE = hardware::vibrator::IVibratorManager::CAP_MIXED_TRIGGER_COMPOSE,
-    TRIGGER_CALLBACK = hardware::vibrator::IVibratorManager::CAP_TRIGGER_CALLBACK
+    SYNC = aidl::android::hardware::vibrator::IVibratorManager::CAP_SYNC,
+    PREPARE_ON = aidl::android::hardware::vibrator::IVibratorManager::CAP_PREPARE_ON,
+    PREPARE_PERFORM = aidl::android::hardware::vibrator::IVibratorManager::CAP_PREPARE_PERFORM,
+    PREPARE_COMPOSE = aidl::android::hardware::vibrator::IVibratorManager::CAP_PREPARE_COMPOSE,
+    MIXED_TRIGGER_ON = aidl::android::hardware::vibrator::IVibratorManager::IVibratorManager::
+            CAP_MIXED_TRIGGER_ON,
+    MIXED_TRIGGER_PERFORM =
+            aidl::android::hardware::vibrator::IVibratorManager::CAP_MIXED_TRIGGER_PERFORM,
+    MIXED_TRIGGER_COMPOSE =
+            aidl::android::hardware::vibrator::IVibratorManager::CAP_MIXED_TRIGGER_COMPOSE,
+    TRIGGER_CALLBACK = aidl::android::hardware::vibrator::IVibratorManager::CAP_TRIGGER_CALLBACK
 };
 
 inline ManagerCapabilities operator|(ManagerCapabilities lhs, ManagerCapabilities rhs) {
@@ -106,8 +109,10 @@
 // Wrapper for the AIDL VibratorManager HAL.
 class AidlManagerHalWrapper : public ManagerHalWrapper {
 public:
+    using VibratorManager = aidl::android::hardware::vibrator::IVibratorManager;
+
     explicit AidlManagerHalWrapper(std::shared_ptr<CallbackScheduler> callbackScheduler,
-                                   sp<hardware::vibrator::IVibratorManager> handle)
+                                   std::shared_ptr<VibratorManager> handle)
           : mHandle(std::move(handle)), mCallbackScheduler(callbackScheduler) {}
     virtual ~AidlManagerHalWrapper() = default;
 
@@ -126,14 +131,14 @@
     std::mutex mHandleMutex;
     std::mutex mCapabilitiesMutex;
     std::mutex mVibratorsMutex;
-    sp<hardware::vibrator::IVibratorManager> mHandle GUARDED_BY(mHandleMutex);
+    std::shared_ptr<VibratorManager> mHandle GUARDED_BY(mHandleMutex);
     std::optional<ManagerCapabilities> mCapabilities GUARDED_BY(mCapabilitiesMutex);
     std::optional<std::vector<int32_t>> mVibratorIds GUARDED_BY(mVibratorsMutex);
     std::unordered_map<int32_t, std::shared_ptr<HalController>> mVibrators
             GUARDED_BY(mVibratorsMutex);
     std::shared_ptr<CallbackScheduler> mCallbackScheduler;
 
-    sp<hardware::vibrator::IVibratorManager> getHal();
+    std::shared_ptr<VibratorManager> getHal();
     std::shared_ptr<HalWrapper> connectToVibrator(int32_t vibratorId,
                                                   std::shared_ptr<CallbackScheduler> scheduler);
 };
diff --git a/services/vibratorservice/test/Android.bp b/services/vibratorservice/test/Android.bp
index be71dc2..cd05123 100644
--- a/services/vibratorservice/test/Android.bp
+++ b/services/vibratorservice/test/Android.bp
@@ -44,12 +44,12 @@
     ],
     shared_libs: [
         "libbase",
-        "libbinder",
+        "libbinder_ndk",
         "libhidlbase",
         "liblog",
         "libvibratorservice",
         "libutils",
-        "android.hardware.vibrator-V2-cpp",
+        "android.hardware.vibrator-V2-ndk",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
index 15fde91..f4c2898 100644
--- a/services/vibratorservice/test/VibratorHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "VibratorHalControllerTest"
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 #include <cutils/atomic.h>
 
 #include <gmock/gmock.h>
@@ -29,10 +29,11 @@
 #include <vibratorservice/VibratorHalController.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
 
 using std::chrono::milliseconds;
 
@@ -46,41 +47,12 @@
 
 // -------------------------------------------------------------------------------------------------
 
-class MockHalWrapper : public vibrator::HalWrapper {
-public:
-    MockHalWrapper(std::shared_ptr<vibrator::CallbackScheduler> scheduler)
-          : HalWrapper(scheduler) {}
-    virtual ~MockHalWrapper() = default;
-
-    MOCK_METHOD(vibrator::HalResult<void>, ping, (), (override));
-    MOCK_METHOD(void, tryReconnect, (), (override));
-    MOCK_METHOD(vibrator::HalResult<void>, on,
-                (milliseconds timeout, const std::function<void()>& completionCallback),
-                (override));
-    MOCK_METHOD(vibrator::HalResult<void>, off, (), (override));
-    MOCK_METHOD(vibrator::HalResult<void>, setAmplitude, (float amplitude), (override));
-    MOCK_METHOD(vibrator::HalResult<void>, setExternalControl, (bool enabled), (override));
-    MOCK_METHOD(vibrator::HalResult<void>, alwaysOnEnable,
-                (int32_t id, Effect effect, EffectStrength strength), (override));
-    MOCK_METHOD(vibrator::HalResult<void>, alwaysOnDisable, (int32_t id), (override));
-    MOCK_METHOD(vibrator::HalResult<milliseconds>, performEffect,
-                (Effect effect, EffectStrength strength,
-                 const std::function<void()>& completionCallback),
-                (override));
-    MOCK_METHOD(vibrator::HalResult<vibrator::Capabilities>, getCapabilitiesInternal, (),
-                (override));
-
-    vibrator::CallbackScheduler* getCallbackScheduler() { return mCallbackScheduler.get(); }
-};
-
-// -------------------------------------------------------------------------------------------------
-
 class VibratorHalControllerTest : public Test {
 public:
     void SetUp() override {
         mConnectCounter = 0;
         auto callbackScheduler = std::make_shared<vibrator::CallbackScheduler>();
-        mMockHal = std::make_shared<StrictMock<MockHalWrapper>>(callbackScheduler);
+        mMockHal = std::make_shared<StrictMock<vibrator::MockHalWrapper>>(callbackScheduler);
         mController = std::make_unique<
                 vibrator::HalController>(std::move(callbackScheduler),
                                          [&](std::shared_ptr<vibrator::CallbackScheduler>) {
@@ -92,7 +64,7 @@
 
 protected:
     int32_t mConnectCounter;
-    std::shared_ptr<MockHalWrapper> mMockHal;
+    std::shared_ptr<vibrator::MockHalWrapper> mMockHal;
     std::unique_ptr<vibrator::HalController> mController;
 };
 
diff --git a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
index 03c9e77..91717f6 100644
--- a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "VibratorHalWrapperAidlTest"
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -27,18 +27,17 @@
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
-using android::binder::Status;
-
-using android::hardware::vibrator::Braking;
-using android::hardware::vibrator::CompositeEffect;
-using android::hardware::vibrator::CompositePrimitive;
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
-using android::hardware::vibrator::IVibrator;
-using android::hardware::vibrator::IVibratorCallback;
-using android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::Braking;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::IVibrator;
+using aidl::android::hardware::vibrator::IVibratorCallback;
+using aidl::android::hardware::vibrator::PrimitivePwle;
 
 using namespace android;
 using namespace std::chrono_literals;
@@ -46,61 +45,10 @@
 
 // -------------------------------------------------------------------------------------------------
 
-class MockBinder : public BBinder {
-public:
-    MOCK_METHOD(status_t, linkToDeath,
-                (const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags), (override));
-    MOCK_METHOD(status_t, unlinkToDeath,
-                (const wp<DeathRecipient>& recipient, void* cookie, uint32_t flags,
-                 wp<DeathRecipient>* outRecipient),
-                (override));
-    MOCK_METHOD(status_t, pingBinder, (), (override));
-};
-
-class MockIVibrator : public IVibrator {
-public:
-    MOCK_METHOD(Status, getCapabilities, (int32_t * ret), (override));
-    MOCK_METHOD(Status, off, (), (override));
-    MOCK_METHOD(Status, on, (int32_t timeout, const sp<IVibratorCallback>& cb), (override));
-    MOCK_METHOD(Status, perform,
-                (Effect e, EffectStrength s, const sp<IVibratorCallback>& cb, int32_t* ret),
-                (override));
-    MOCK_METHOD(Status, getSupportedEffects, (std::vector<Effect> * ret), (override));
-    MOCK_METHOD(Status, setAmplitude, (float amplitude), (override));
-    MOCK_METHOD(Status, setExternalControl, (bool enabled), (override));
-    MOCK_METHOD(Status, getCompositionDelayMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getCompositionSizeMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getSupportedPrimitives, (std::vector<CompositePrimitive> * ret),
-                (override));
-    MOCK_METHOD(Status, getPrimitiveDuration, (CompositePrimitive p, int32_t* ret), (override));
-    MOCK_METHOD(Status, compose,
-                (const std::vector<CompositeEffect>& e, const sp<IVibratorCallback>& cb),
-                (override));
-    MOCK_METHOD(Status, composePwle,
-                (const std::vector<PrimitivePwle>& e, const sp<IVibratorCallback>& cb), (override));
-    MOCK_METHOD(Status, getSupportedAlwaysOnEffects, (std::vector<Effect> * ret), (override));
-    MOCK_METHOD(Status, alwaysOnEnable, (int32_t id, Effect e, EffectStrength s), (override));
-    MOCK_METHOD(Status, alwaysOnDisable, (int32_t id), (override));
-    MOCK_METHOD(Status, getQFactor, (float * ret), (override));
-    MOCK_METHOD(Status, getResonantFrequency, (float * ret), (override));
-    MOCK_METHOD(Status, getFrequencyResolution, (float* ret), (override));
-    MOCK_METHOD(Status, getFrequencyMinimum, (float* ret), (override));
-    MOCK_METHOD(Status, getBandwidthAmplitudeMap, (std::vector<float> * ret), (override));
-    MOCK_METHOD(Status, getPwlePrimitiveDurationMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getPwleCompositionSizeMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getSupportedBraking, (std::vector<Braking> * ret), (override));
-    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
-    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
-    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
-};
-
-// -------------------------------------------------------------------------------------------------
-
 class VibratorHalWrapperAidlTest : public Test {
 public:
     void SetUp() override {
-        mMockBinder = new StrictMock<MockBinder>();
-        mMockHal = new StrictMock<MockIVibrator>();
+        mMockHal = ndk::SharedRefBase::make<StrictMock<vibrator::MockIVibrator>>();
         mMockScheduler = std::make_shared<StrictMock<vibrator::MockCallbackScheduler>>();
         mWrapper = std::make_unique<vibrator::AidlHalWrapper>(mMockScheduler, mMockHal);
         ASSERT_NE(mWrapper, nullptr);
@@ -109,54 +57,28 @@
 protected:
     std::shared_ptr<StrictMock<vibrator::MockCallbackScheduler>> mMockScheduler = nullptr;
     std::unique_ptr<vibrator::HalWrapper> mWrapper = nullptr;
-    sp<StrictMock<MockIVibrator>> mMockHal = nullptr;
-    sp<StrictMock<MockBinder>> mMockBinder = nullptr;
+    std::shared_ptr<StrictMock<vibrator::MockIVibrator>> mMockHal = nullptr;
 };
 
 // -------------------------------------------------------------------------------------------------
 
-ACTION(TriggerCallbackInArg1) {
-    if (arg1 != nullptr) {
-        arg1->onComplete();
-    }
-}
-
-ACTION(TriggerCallbackInArg2) {
-    if (arg2 != nullptr) {
-        arg2->onComplete();
-    }
-}
-
-TEST_F(VibratorHalWrapperAidlTest, TestPing) {
-    EXPECT_CALL(*mMockHal.get(), onAsBinder())
-            .Times(Exactly(2))
-            .WillRepeatedly(Return(mMockBinder.get()));
-    EXPECT_CALL(*mMockBinder.get(), pingBinder())
-            .Times(Exactly(2))
-            .WillOnce(Return(android::OK))
-            .WillRepeatedly(Return(android::DEAD_OBJECT));
-
-    ASSERT_TRUE(mWrapper->ping().isOk());
-    ASSERT_TRUE(mWrapper->ping().isFailed());
-}
-
 TEST_F(VibratorHalWrapperAidlTest, TestOnWithCallbackSupport) {
     {
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(
-                        DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), on(Eq(10), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), on(Eq(100), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
         EXPECT_CALL(*mMockHal.get(), on(Eq(1000), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -179,20 +101,20 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(
-                        DoAll(SetArgPointee<0>(IVibrator::CAP_COMPOSE_EFFECTS), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(IVibrator::CAP_COMPOSE_EFFECTS),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), on(Eq(10), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status()));
+                .WillOnce(Return(ndk::ScopedAStatus::ok()));
         EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
                 .Times(Exactly(1))
-                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
+                .WillOnce(vibrator::TriggerSchedulerCallback());
         EXPECT_CALL(*mMockHal.get(), on(Eq(11), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(), on(Eq(12), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -211,10 +133,9 @@
 TEST_F(VibratorHalWrapperAidlTest, TestOff) {
     EXPECT_CALL(*mMockHal.get(), off())
             .Times(Exactly(3))
-            .WillOnce(Return(Status()))
-            .WillOnce(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
-            .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+            .WillOnce(Return(ndk::ScopedAStatus::ok()))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
 
     ASSERT_TRUE(mWrapper->off().isOk());
     ASSERT_TRUE(mWrapper->off().isUnsupported());
@@ -224,13 +145,15 @@
 TEST_F(VibratorHalWrapperAidlTest, TestSetAmplitude) {
     {
         InSequence seq;
-        EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(0.1f))).Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(0.1f)))
+                .Times(Exactly(1))
+                .WillOnce(Return(ndk::ScopedAStatus::ok()));
         EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(0.2f)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(0.5f)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     ASSERT_TRUE(mWrapper->setAmplitude(0.1f).isOk());
@@ -241,12 +164,13 @@
 TEST_F(VibratorHalWrapperAidlTest, TestSetExternalControl) {
     {
         InSequence seq;
-        EXPECT_CALL(*mMockHal.get(), setExternalControl(Eq(true))).Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), setExternalControl(Eq(true)))
+                .Times(Exactly(1))
+                .WillOnce(Return(ndk::ScopedAStatus::ok()));
         EXPECT_CALL(*mMockHal.get(), setExternalControl(Eq(false)))
                 .Times(Exactly(2))
-                .WillOnce(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     ASSERT_TRUE(mWrapper->setExternalControl(true).isOk());
@@ -259,15 +183,16 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(),
                     alwaysOnEnable(Eq(1), Eq(Effect::CLICK), Eq(EffectStrength::LIGHT)))
-                .Times(Exactly(1));
+                .Times(Exactly(1))
+                .WillOnce(Return(ndk::ScopedAStatus::ok()));
         EXPECT_CALL(*mMockHal.get(),
                     alwaysOnEnable(Eq(2), Eq(Effect::TICK), Eq(EffectStrength::MEDIUM)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(),
                     alwaysOnEnable(Eq(3), Eq(Effect::POP), Eq(EffectStrength::STRONG)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     auto result = mWrapper->alwaysOnEnable(1, Effect::CLICK, EffectStrength::LIGHT);
@@ -281,14 +206,15 @@
 TEST_F(VibratorHalWrapperAidlTest, TestAlwaysOnDisable) {
     {
         InSequence seq;
-        EXPECT_CALL(*mMockHal.get(), alwaysOnDisable(Eq(1))).Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), alwaysOnDisable(Eq(1)))
+                .Times(Exactly(1))
+                .WillOnce(Return(ndk::ScopedAStatus::ok()));
         EXPECT_CALL(*mMockHal.get(), alwaysOnDisable(Eq(2)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
         EXPECT_CALL(*mMockHal.get(), alwaysOnDisable(Eq(3)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     ASSERT_TRUE(mWrapper->alwaysOnDisable(1).isOk());
@@ -311,66 +237,70 @@
     std::vector<float> amplitudes = {0.f, 1.f, 0.f};
 
     std::vector<std::chrono::milliseconds> primitiveDurations;
-    constexpr auto primitiveRange = enum_range<CompositePrimitive>();
+    constexpr auto primitiveRange = ndk::enum_range<CompositePrimitive>();
     constexpr auto primitiveCount = std::distance(primitiveRange.begin(), primitiveRange.end());
     primitiveDurations.resize(primitiveCount);
     primitiveDurations[static_cast<size_t>(CompositePrimitive::CLICK)] = 10ms;
 
     EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK),
+                            Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getSupportedEffects(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(supportedEffects), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(supportedEffects), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getSupportedBraking(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(supportedBraking), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(supportedBraking), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getSupportedPrimitives(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(supportedPrimitives), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(
+                    DoAll(SetArgPointee<0>(supportedPrimitives), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::CLICK), _))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(10), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<1>(10), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getCompositionSizeMax(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(COMPOSITION_SIZE_MAX), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(COMPOSITION_SIZE_MAX),
+                            Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getCompositionDelayMax(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(PRIMITIVE_DELAY_MAX), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(
+                    DoAll(SetArgPointee<0>(PRIMITIVE_DELAY_MAX), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getPwlePrimitiveDurationMax(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(PWLE_DURATION_MAX), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_DURATION_MAX), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getPwleCompositionSizeMax(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(PWLE_SIZE_MAX), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_SIZE_MAX), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getFrequencyMinimum(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(F_MIN), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(F_MIN), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getResonantFrequency(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(F0), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(F0), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getFrequencyResolution(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(F_RESOLUTION), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(F_RESOLUTION), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getQFactor(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(Q_FACTOR), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(Q_FACTOR), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getBandwidthAmplitudeMap(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(amplitudes), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(amplitudes), Return(ndk::ScopedAStatus::ok())));
 
     vibrator::Info failed = mWrapper->getInfo();
     ASSERT_TRUE(failed.capabilities.isFailed());
@@ -417,46 +347,46 @@
 
     EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK),
+                            Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getSupportedEffects(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(supportedEffects), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(supportedEffects), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getQFactor(_))
             .Times(Exactly(1))
-            .WillRepeatedly(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
     EXPECT_CALL(*mMockHal.get(), getSupportedPrimitives(_))
             .Times(Exactly(1))
-            .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+            .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
     EXPECT_CALL(*mMockHal.get(), getCompositionSizeMax(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(COMPOSITION_SIZE_MAX), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(COMPOSITION_SIZE_MAX),
+                            Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getCompositionDelayMax(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(PRIMITIVE_DELAY_MAX), Return(Status())));
+            .WillOnce(
+                    DoAll(SetArgPointee<0>(PRIMITIVE_DELAY_MAX), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getPwlePrimitiveDurationMax(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(PWLE_DURATION_MAX), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_DURATION_MAX), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getPwleCompositionSizeMax(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(PWLE_SIZE_MAX), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_SIZE_MAX), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getFrequencyMinimum(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(F_MIN), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(F_MIN), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getResonantFrequency(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(F0), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(F0), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getFrequencyResolution(_))
             .Times(Exactly(1))
-            .WillRepeatedly(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
     EXPECT_CALL(*mMockHal.get(), getBandwidthAmplitudeMap(_))
             .Times(Exactly(1))
-            .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+            .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
     EXPECT_CALL(*mMockHal.get(), getSupportedBraking(_))
             .Times(Exactly(1))
-            .WillRepeatedly(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
 
     std::vector<std::thread> threads;
     for (int i = 0; i < 10; i++) {
@@ -487,18 +417,18 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(
-                        DoAll(SetArgPointee<0>(IVibrator::CAP_PERFORM_CALLBACK), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(IVibrator::CAP_PERFORM_CALLBACK),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::CLICK), Eq(EffectStrength::LIGHT), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(
-                        DoAll(SetArgPointee<3>(1000), TriggerCallbackInArg2(), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<3>(1000), WithArg<2>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::POP), Eq(EffectStrength::MEDIUM), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::THUD), Eq(EffectStrength::STRONG), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -525,21 +455,20 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(
-                        DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::CLICK), Eq(EffectStrength::LIGHT), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<3>(10), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<3>(10), Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
                 .Times(Exactly(1))
-                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
+                .WillOnce(vibrator::TriggerSchedulerCallback());
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::POP), Eq(EffectStrength::MEDIUM), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::THUD), Eq(EffectStrength::STRONG), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -576,26 +505,28 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getSupportedPrimitives(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<0>(supportedPrimitives), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(supportedPrimitives),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::CLICK), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(1), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(1), Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::SPIN), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(2), Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::THUD), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(3), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(3), Return(ndk::ScopedAStatus::ok())));
 
         EXPECT_CALL(*mMockHal.get(), compose(Eq(emptyEffects), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(singleEffect), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(multipleEffects), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -630,26 +561,32 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getSupportedPrimitives(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<0>(supportedPrimitives), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(supportedPrimitives),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::SPIN), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(2), Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::THUD), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(multipleEffects), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
 
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::SPIN), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(2), Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::THUD), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(2), Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(multipleEffects), _))
                 .Times(Exactly(2))
-                .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+                // ndk::ScopedAStatus::ok() cannot be copy-constructed so can't use WillRepeatedly
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())))
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -680,12 +617,12 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), composePwle(Eq(emptyPrimitives), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
         EXPECT_CALL(*mMockHal.get(), composePwle(Eq(multiplePrimitives), _))
                 .Times(Exactly(2))
-                .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-                .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
index 0c27fc7..dd59093 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "VibratorHalWrapperHidlV1_0Test"
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -27,17 +27,18 @@
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
 namespace V1_0 = android::hardware::vibrator::V1_0;
 
-using android::hardware::vibrator::Braking;
-using android::hardware::vibrator::CompositeEffect;
-using android::hardware::vibrator::CompositePrimitive;
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
-using android::hardware::vibrator::IVibrator;
-using android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::Braking;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::IVibrator;
+using aidl::android::hardware::vibrator::PrimitivePwle;
 
 using namespace android;
 using namespace std::chrono_literals;
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp
index d887efc..b0a6537 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "VibratorHalWrapperHidlV1_1Test"
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -26,13 +26,14 @@
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
 namespace V1_0 = android::hardware::vibrator::V1_0;
 namespace V1_1 = android::hardware::vibrator::V1_1;
 
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
 
 using namespace android;
 using namespace std::chrono_literals;
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp
index 26d9350..dfe3fa0 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "VibratorHalWrapperHidlV1_2Test"
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -26,14 +26,15 @@
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
 namespace V1_0 = android::hardware::vibrator::V1_0;
 namespace V1_1 = android::hardware::vibrator::V1_1;
 namespace V1_2 = android::hardware::vibrator::V1_2;
 
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
 
 using namespace android;
 using namespace std::chrono_literals;
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp
index a6f1a74..8624332 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "VibratorHalWrapperHidlV1_3Test"
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -27,6 +27,7 @@
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
 namespace V1_0 = android::hardware::vibrator::V1_0;
@@ -34,9 +35,9 @@
 namespace V1_2 = android::hardware::vibrator::V1_2;
 namespace V1_3 = android::hardware::vibrator::V1_3;
 
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
-using android::hardware::vibrator::IVibrator;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::IVibrator;
 
 using namespace android;
 using namespace std::chrono_literals;
diff --git a/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
index 11a8b66..c7214e0 100644
--- a/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
@@ -24,6 +24,7 @@
 
 #include <vibratorservice/VibratorManagerHalController.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
 using android::vibrator::HalController;
@@ -35,6 +36,8 @@
 static const std::vector<int32_t> VIBRATOR_IDS = {1, 2};
 static constexpr int VIBRATOR_ID = 1;
 
+// -------------------------------------------------------------------------------------------------
+
 class MockManagerHalWrapper : public vibrator::ManagerHalWrapper {
 public:
     MOCK_METHOD(void, tryReconnect, (), (override));
@@ -51,6 +54,8 @@
     MOCK_METHOD(vibrator::HalResult<void>, cancelSynced, (), (override));
 };
 
+// -------------------------------------------------------------------------------------------------
+
 class VibratorManagerHalControllerTest : public Test {
 public:
     void SetUp() override {
@@ -106,6 +111,8 @@
     }
 };
 
+// -------------------------------------------------------------------------------------------------
+
 TEST_F(VibratorManagerHalControllerTest, TestInit) {
     mController->init();
     ASSERT_EQ(1, mConnectCounter);
diff --git a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
index dffc281..764d9be 100644
--- a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
@@ -23,84 +23,42 @@
 
 #include <vibratorservice/VibratorManagerHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
-using android::binder::Status;
-
-using android::hardware::vibrator::Braking;
-using android::hardware::vibrator::CompositeEffect;
-using android::hardware::vibrator::CompositePrimitive;
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
-using android::hardware::vibrator::IVibrator;
-using android::hardware::vibrator::IVibratorCallback;
-using android::hardware::vibrator::IVibratorManager;
-using android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::Braking;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::IVibrator;
+using aidl::android::hardware::vibrator::IVibratorCallback;
+using aidl::android::hardware::vibrator::IVibratorManager;
+using aidl::android::hardware::vibrator::PrimitivePwle;
 
 using namespace android;
 using namespace testing;
 
 static const auto OFF_FN = [](vibrator::HalWrapper* hal) { return hal->off(); };
 
-class MockBinder : public BBinder {
-public:
-    MOCK_METHOD(status_t, linkToDeath,
-                (const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags), (override));
-    MOCK_METHOD(status_t, unlinkToDeath,
-                (const wp<DeathRecipient>& recipient, void* cookie, uint32_t flags,
-                 wp<DeathRecipient>* outRecipient),
-                (override));
-    MOCK_METHOD(status_t, pingBinder, (), (override));
-};
-
-class MockIVibrator : public IVibrator {
-public:
-    MOCK_METHOD(Status, getCapabilities, (int32_t * ret), (override));
-    MOCK_METHOD(Status, off, (), (override));
-    MOCK_METHOD(Status, on, (int32_t timeout, const sp<IVibratorCallback>& cb), (override));
-    MOCK_METHOD(Status, perform,
-                (Effect e, EffectStrength s, const sp<IVibratorCallback>& cb, int32_t* ret),
-                (override));
-    MOCK_METHOD(Status, getSupportedEffects, (std::vector<Effect> * ret), (override));
-    MOCK_METHOD(Status, setAmplitude, (float amplitude), (override));
-    MOCK_METHOD(Status, setExternalControl, (bool enabled), (override));
-    MOCK_METHOD(Status, getCompositionDelayMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getCompositionSizeMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getSupportedPrimitives, (std::vector<CompositePrimitive> * ret),
-                (override));
-    MOCK_METHOD(Status, getPrimitiveDuration, (CompositePrimitive p, int32_t* ret), (override));
-    MOCK_METHOD(Status, compose,
-                (const std::vector<CompositeEffect>& e, const sp<IVibratorCallback>& cb),
-                (override));
-    MOCK_METHOD(Status, composePwle,
-                (const std::vector<PrimitivePwle>& e, const sp<IVibratorCallback>& cb), (override));
-    MOCK_METHOD(Status, getSupportedAlwaysOnEffects, (std::vector<Effect> * ret), (override));
-    MOCK_METHOD(Status, alwaysOnEnable, (int32_t id, Effect e, EffectStrength s), (override));
-    MOCK_METHOD(Status, alwaysOnDisable, (int32_t id), (override));
-    MOCK_METHOD(Status, getQFactor, (float * ret), (override));
-    MOCK_METHOD(Status, getResonantFrequency, (float * ret), (override));
-    MOCK_METHOD(Status, getFrequencyResolution, (float* ret), (override));
-    MOCK_METHOD(Status, getFrequencyMinimum, (float* ret), (override));
-    MOCK_METHOD(Status, getBandwidthAmplitudeMap, (std::vector<float> * ret), (override));
-    MOCK_METHOD(Status, getPwlePrimitiveDurationMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getPwleCompositionSizeMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getSupportedBraking, (std::vector<Braking> * ret), (override));
-    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
-    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
-    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
-};
+// -------------------------------------------------------------------------------------------------
 
 class MockIVibratorManager : public IVibratorManager {
 public:
-    MOCK_METHOD(Status, getCapabilities, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getVibratorIds, (std::vector<int32_t> * ret), (override));
-    MOCK_METHOD(Status, getVibrator, (int32_t id, sp<IVibrator>* ret), (override));
-    MOCK_METHOD(Status, prepareSynced, (const std::vector<int32_t>& ids), (override));
-    MOCK_METHOD(Status, triggerSynced, (const sp<IVibratorCallback>& cb), (override));
-    MOCK_METHOD(Status, cancelSynced, (), (override));
-    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
-    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
-    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+    MockIVibratorManager() = default;
+
+    MOCK_METHOD(ndk::ScopedAStatus, getCapabilities, (int32_t * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getVibratorIds, (std::vector<int32_t> * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getVibrator, (int32_t id, std::shared_ptr<IVibrator>* ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, prepareSynced, (const std::vector<int32_t>& ids), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, triggerSynced, (const std::shared_ptr<IVibratorCallback>& cb),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, cancelSynced, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t*), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string*), (override));
+    MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
 };
 
 // -------------------------------------------------------------------------------------------------
@@ -108,9 +66,8 @@
 class VibratorManagerHalWrapperAidlTest : public Test {
 public:
     void SetUp() override {
-        mMockBinder = new StrictMock<MockBinder>();
-        mMockVibrator = new StrictMock<MockIVibrator>();
-        mMockHal = new StrictMock<MockIVibratorManager>();
+        mMockVibrator = ndk::SharedRefBase::make<StrictMock<vibrator::MockIVibrator>>();
+        mMockHal = ndk::SharedRefBase::make<StrictMock<MockIVibratorManager>>();
         mMockScheduler = std::make_shared<StrictMock<vibrator::MockCallbackScheduler>>();
         mWrapper = std::make_unique<vibrator::AidlManagerHalWrapper>(mMockScheduler, mMockHal);
         ASSERT_NE(mWrapper, nullptr);
@@ -119,9 +76,8 @@
 protected:
     std::shared_ptr<StrictMock<vibrator::MockCallbackScheduler>> mMockScheduler = nullptr;
     std::unique_ptr<vibrator::ManagerHalWrapper> mWrapper = nullptr;
-    sp<StrictMock<MockIVibratorManager>> mMockHal = nullptr;
-    sp<StrictMock<MockIVibrator>> mMockVibrator = nullptr;
-    sp<StrictMock<MockBinder>> mMockBinder = nullptr;
+    std::shared_ptr<StrictMock<MockIVibratorManager>> mMockHal = nullptr;
+    std::shared_ptr<StrictMock<vibrator::MockIVibrator>> mMockVibrator = nullptr;
 };
 
 // -------------------------------------------------------------------------------------------------
@@ -129,32 +85,13 @@
 static const std::vector<int32_t> kVibratorIds = {1, 2};
 static constexpr int kVibratorId = 1;
 
-ACTION(TriggerCallback) {
-    if (arg0 != nullptr) {
-        arg0->onComplete();
-    }
-}
-
-TEST_F(VibratorManagerHalWrapperAidlTest, TestPing) {
-    EXPECT_CALL(*mMockHal.get(), onAsBinder())
-            .Times(Exactly(2))
-            .WillRepeatedly(Return(mMockBinder.get()));
-    EXPECT_CALL(*mMockBinder.get(), pingBinder())
-            .Times(Exactly(2))
-            .WillOnce(Return(android::OK))
-            .WillRepeatedly(Return(android::DEAD_OBJECT));
-
-    ASSERT_TRUE(mWrapper->ping().isOk());
-    ASSERT_TRUE(mWrapper->ping().isFailed());
-}
-
 TEST_F(VibratorManagerHalWrapperAidlTest, TestGetCapabilitiesDoesNotCacheFailedResult) {
     EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
             .Times(Exactly(3))
-            .WillOnce(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC),
+                            Return(ndk::ScopedAStatus::ok())));
 
     ASSERT_TRUE(mWrapper->getCapabilities().isUnsupported());
     ASSERT_TRUE(mWrapper->getCapabilities().isFailed());
@@ -167,7 +104,8 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestGetCapabilitiesCachesResult) {
     EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC),
+                            Return(ndk::ScopedAStatus::ok())));
 
     std::vector<std::thread> threads;
     for (int i = 0; i < 10; i++) {
@@ -187,10 +125,9 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorIdsDoesNotCacheFailedResult) {
     EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
             .Times(Exactly(3))
-            .WillOnce(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
     ASSERT_TRUE(mWrapper->getVibratorIds().isUnsupported());
     ASSERT_TRUE(mWrapper->getVibratorIds().isFailed());
@@ -203,7 +140,7 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorIdsCachesResult) {
     EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
     std::vector<std::thread> threads;
     for (int i = 0; i < 10; i++) {
@@ -225,11 +162,11 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
         EXPECT_CALL(*mMockHal.get(), getVibrator(Eq(kVibratorId), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())));
     }
 
     auto result = mWrapper->getVibrator(kVibratorId);
@@ -241,7 +178,7 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorWithInvalidIdFails) {
     EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
     ASSERT_TRUE(mWrapper->getVibrator(0).isFailed());
 }
@@ -249,20 +186,21 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorRecoversVibratorPointer) {
     EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
     EXPECT_CALL(*mMockHal.get(), getVibrator(Eq(kVibratorId), _))
             .Times(Exactly(3))
             .WillOnce(DoAll(SetArgPointee<1>(nullptr),
-                            Return(Status::fromExceptionCode(
-                                    Status::Exception::EX_TRANSACTION_FAILED))))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+                            Return(ndk::ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED))))
+            // ndk::ScopedAStatus::ok() cannot be copy-constructed so can't use WillRepeatedly
+            .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())))
+            .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())));
 
     EXPECT_CALL(*mMockVibrator.get(), off())
             .Times(Exactly(3))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_TRANSACTION_FAILED)))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_TRANSACTION_FAILED)))
-            .WillRepeatedly(Return(Status()));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED)))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED)))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
 
     // Get vibrator controller is successful even if first getVibrator.
     auto result = mWrapper->getVibrator(kVibratorId);
@@ -281,18 +219,19 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestPrepareSynced) {
     EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
     EXPECT_CALL(*mMockHal.get(), getVibrator(_, _))
             .Times(Exactly(2))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+            // ndk::ScopedAStatus::ok() cannot be copy-constructed so can't use WillRepeatedly
+            .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())))
+            .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())));
 
     EXPECT_CALL(*mMockHal.get(), prepareSynced(Eq(kVibratorIds)))
             .Times(Exactly(3))
-            .WillOnce(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(Return(Status()));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
 
     ASSERT_TRUE(mWrapper->getVibratorIds().isOk());
     ASSERT_TRUE(mWrapper->prepareSynced(kVibratorIds).isUnsupported());
@@ -305,13 +244,13 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<0>(IVibratorManager::CAP_TRIGGER_CALLBACK),
-                                      Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(IVibratorManager::CAP_TRIGGER_CALLBACK),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), triggerSynced(_))
                 .Times(Exactly(3))
-                .WillOnce(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)))
-                .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-                .WillRepeatedly(DoAll(TriggerCallback(), Return(Status())));
+                .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)))
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+                .WillOnce(DoAll(vibrator::TriggerCallback(), Return(ndk::ScopedAStatus::ok())));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -328,11 +267,11 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(
-                        DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), triggerSynced(Eq(nullptr)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status()));
+                .WillOnce(Return(ndk::ScopedAStatus::ok()));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -345,9 +284,9 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestCancelSynced) {
     EXPECT_CALL(*mMockHal.get(), cancelSynced())
             .Times(Exactly(3))
-            .WillOnce(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(Return(Status()));
+            .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
 
     ASSERT_TRUE(mWrapper->cancelSynced().isUnsupported());
     ASSERT_TRUE(mWrapper->cancelSynced().isFailed());
@@ -357,13 +296,17 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestCancelSyncedReloadsAllControllers) {
     EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
     EXPECT_CALL(*mMockHal.get(), getVibrator(_, _))
             .Times(Exactly(2))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+            // ndk::ScopedAStatus::ok() cannot be copy-constructed so can't use WillRepeatedly
+            .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())))
+            .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())));
 
-    EXPECT_CALL(*mMockHal.get(), cancelSynced()).Times(Exactly(1)).WillRepeatedly(Return(Status()));
+    EXPECT_CALL(*mMockHal.get(), cancelSynced())
+            .Times(Exactly(1))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
 
     ASSERT_TRUE(mWrapper->getVibratorIds().isOk());
     ASSERT_TRUE(mWrapper->cancelSynced().isOk());
diff --git a/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp b/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp
index 0850ef3..7877236 100644
--- a/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp
@@ -23,10 +23,12 @@
 
 #include <vibratorservice/VibratorManagerHalWrapper.h>
 
-using android::hardware::vibrator::CompositeEffect;
-using android::hardware::vibrator::CompositePrimitive;
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
+#include "test_mocks.h"
+
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
 
 using std::chrono::milliseconds;
 
@@ -35,27 +37,16 @@
 
 // -------------------------------------------------------------------------------------------------
 
-class MockHalController : public vibrator::HalController {
-public:
-    MockHalController() = default;
-    virtual ~MockHalController() = default;
-
-    MOCK_METHOD(bool, init, (), (override));
-    MOCK_METHOD(void, tryReconnect, (), (override));
-};
-
-// -------------------------------------------------------------------------------------------------
-
 class VibratorManagerHalWrapperLegacyTest : public Test {
 public:
     void SetUp() override {
-        mMockController = std::make_shared<StrictMock<MockHalController>>();
+        mMockController = std::make_shared<StrictMock<vibrator::MockHalController>>();
         mWrapper = std::make_unique<vibrator::LegacyManagerHalWrapper>(mMockController);
         ASSERT_NE(mWrapper, nullptr);
     }
 
 protected:
-    std::shared_ptr<StrictMock<MockHalController>> mMockController = nullptr;
+    std::shared_ptr<StrictMock<vibrator::MockHalController>> mMockController = nullptr;
     std::unique_ptr<vibrator::ManagerHalWrapper> mWrapper = nullptr;
 };
 
diff --git a/services/vibratorservice/test/test_mocks.h b/services/vibratorservice/test/test_mocks.h
new file mode 100644
index 0000000..7882186
--- /dev/null
+++ b/services/vibratorservice/test/test_mocks.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *            http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef VIBRATORSERVICE_UNITTEST_MOCKS_H_
+#define VIBRATORSERVICE_UNITTEST_MOCKS_H_
+
+#include <gmock/gmock.h>
+
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+
+#include <vibratorservice/VibratorCallbackScheduler.h>
+#include <vibratorservice/VibratorHalController.h>
+#include <vibratorservice/VibratorHalWrapper.h>
+
+namespace android {
+
+namespace vibrator {
+
+using std::chrono::milliseconds;
+
+using namespace testing;
+
+using aidl::android::hardware::vibrator::Braking;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::IVibrator;
+using aidl::android::hardware::vibrator::IVibratorCallback;
+using aidl::android::hardware::vibrator::PrimitivePwle;
+
+// -------------------------------------------------------------------------------------------------
+
+class MockIVibrator : public IVibrator {
+public:
+    MockIVibrator() = default;
+
+    MOCK_METHOD(ndk::ScopedAStatus, getCapabilities, (int32_t * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, off, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, on,
+                (int32_t timeout, const std::shared_ptr<IVibratorCallback>& cb), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, perform,
+                (Effect e, EffectStrength s, const std::shared_ptr<IVibratorCallback>& cb,
+                 int32_t* ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSupportedEffects, (std::vector<Effect> * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, setAmplitude, (float amplitude), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, setExternalControl, (bool enabled), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getCompositionDelayMax, (int32_t * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getCompositionSizeMax, (int32_t * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSupportedPrimitives, (std::vector<CompositePrimitive> * ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getPrimitiveDuration, (CompositePrimitive p, int32_t* ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, compose,
+                (const std::vector<CompositeEffect>& e,
+                 const std::shared_ptr<IVibratorCallback>& cb),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, composePwle,
+                (const std::vector<PrimitivePwle>& e, const std::shared_ptr<IVibratorCallback>& cb),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSupportedAlwaysOnEffects, (std::vector<Effect> * ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, alwaysOnEnable, (int32_t id, Effect e, EffectStrength s),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, alwaysOnDisable, (int32_t id), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getQFactor, (float* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getResonantFrequency, (float* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getFrequencyResolution, (float* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getFrequencyMinimum, (float* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getBandwidthAmplitudeMap, (std::vector<float> * ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getPwlePrimitiveDurationMax, (int32_t * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getPwleCompositionSizeMax, (int32_t * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSupportedBraking, (std::vector<Braking> * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t*), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string*), (override));
+    MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
+};
+
+// gmock requirement to provide a WithArg<0>(TriggerCallback()) matcher
+typedef void TriggerCallbackFunction(const std::shared_ptr<IVibratorCallback>&);
+
+class TriggerCallbackAction : public ActionInterface<TriggerCallbackFunction> {
+public:
+    explicit TriggerCallbackAction() {}
+
+    virtual Result Perform(const ArgumentTuple& args) {
+        const std::shared_ptr<IVibratorCallback>& callback = get<0>(args);
+        if (callback) {
+            callback->onComplete();
+        }
+    }
+};
+
+inline Action<TriggerCallbackFunction> TriggerCallback() {
+    return MakeAction(new TriggerCallbackAction());
+}
+
+// -------------------------------------------------------------------------------------------------
+
+class MockCallbackScheduler : public CallbackScheduler {
+public:
+    MOCK_METHOD(void, schedule, (std::function<void()> callback, std::chrono::milliseconds delay),
+                (override));
+};
+
+ACTION(TriggerSchedulerCallback) {
+    arg0();
+}
+
+// -------------------------------------------------------------------------------------------------
+
+class MockHalWrapper : public HalWrapper {
+public:
+    MockHalWrapper(std::shared_ptr<CallbackScheduler> scheduler) : HalWrapper(scheduler) {}
+    virtual ~MockHalWrapper() = default;
+
+    MOCK_METHOD(vibrator::HalResult<void>, ping, (), (override));
+    MOCK_METHOD(void, tryReconnect, (), (override));
+    MOCK_METHOD(vibrator::HalResult<void>, on,
+                (milliseconds timeout, const std::function<void()>& completionCallback),
+                (override));
+    MOCK_METHOD(vibrator::HalResult<void>, off, (), (override));
+    MOCK_METHOD(vibrator::HalResult<void>, setAmplitude, (float amplitude), (override));
+    MOCK_METHOD(vibrator::HalResult<void>, setExternalControl, (bool enabled), (override));
+    MOCK_METHOD(vibrator::HalResult<void>, alwaysOnEnable,
+                (int32_t id, Effect effect, EffectStrength strength), (override));
+    MOCK_METHOD(vibrator::HalResult<void>, alwaysOnDisable, (int32_t id), (override));
+    MOCK_METHOD(vibrator::HalResult<milliseconds>, performEffect,
+                (Effect effect, EffectStrength strength,
+                 const std::function<void()>& completionCallback),
+                (override));
+    MOCK_METHOD(vibrator::HalResult<vibrator::Capabilities>, getCapabilitiesInternal, (),
+                (override));
+
+    CallbackScheduler* getCallbackScheduler() { return mCallbackScheduler.get(); }
+};
+
+class MockHalController : public vibrator::HalController {
+public:
+    MockHalController() = default;
+    virtual ~MockHalController() = default;
+
+    MOCK_METHOD(bool, init, (), (override));
+    MOCK_METHOD(void, tryReconnect, (), (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+} // namespace vibrator
+
+} // namespace android
+
+#endif // VIBRATORSERVICE_UNITTEST_MOCKS_H_
diff --git a/services/vibratorservice/test/test_utils.h b/services/vibratorservice/test/test_utils.h
index 715c221..e99965c 100644
--- a/services/vibratorservice/test/test_utils.h
+++ b/services/vibratorservice/test/test_utils.h
@@ -17,7 +17,7 @@
 #ifndef VIBRATORSERVICE_UNITTEST_UTIL_H_
 #define VIBRATORSERVICE_UNITTEST_UTIL_H_
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 
 #include <vibratorservice/VibratorHalWrapper.h>
 
@@ -25,24 +25,12 @@
 
 namespace vibrator {
 
-using ::android::hardware::vibrator::ActivePwle;
-using ::android::hardware::vibrator::Braking;
-using ::android::hardware::vibrator::BrakingPwle;
-using ::android::hardware::vibrator::CompositeEffect;
-using ::android::hardware::vibrator::CompositePrimitive;
-using ::android::hardware::vibrator::PrimitivePwle;
-
-// -------------------------------------------------------------------------------------------------
-
-class MockCallbackScheduler : public vibrator::CallbackScheduler {
-public:
-    MOCK_METHOD(void, schedule, (std::function<void()> callback, std::chrono::milliseconds delay),
-                (override));
-};
-
-ACTION(TriggerSchedulerCallback) {
-    arg0();
-}
+using aidl::android::hardware::vibrator::ActivePwle;
+using aidl::android::hardware::vibrator::Braking;
+using aidl::android::hardware::vibrator::BrakingPwle;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::PrimitivePwle;
 
 // -------------------------------------------------------------------------------------------------
 
diff --git a/vulkan/libvulkan/api_gen.cpp b/vulkan/libvulkan/api_gen.cpp
index a9706bc..a3fe33e 100644
--- a/vulkan/libvulkan/api_gen.cpp
+++ b/vulkan/libvulkan/api_gen.cpp
@@ -178,7 +178,7 @@
     INIT_PROC(false, instance, GetPhysicalDeviceExternalSemaphoreProperties);
     INIT_PROC(false, instance, GetPhysicalDeviceExternalFenceProperties);
     INIT_PROC(false, instance, EnumeratePhysicalDeviceGroups);
-    INIT_PROC_EXT(KHR_swapchain, false, instance, GetPhysicalDevicePresentRectanglesKHR);
+    INIT_PROC_EXT(KHR_swapchain, true, instance, GetPhysicalDevicePresentRectanglesKHR);
     INIT_PROC(false, instance, GetPhysicalDeviceToolProperties);
     // clang-format on
 
@@ -325,9 +325,9 @@
     INIT_PROC(false, dev, BindBufferMemory2);
     INIT_PROC(false, dev, BindImageMemory2);
     INIT_PROC(false, dev, CmdSetDeviceMask);
-    INIT_PROC_EXT(KHR_swapchain, false, dev, GetDeviceGroupPresentCapabilitiesKHR);
-    INIT_PROC_EXT(KHR_swapchain, false, dev, GetDeviceGroupSurfacePresentModesKHR);
-    INIT_PROC_EXT(KHR_swapchain, false, dev, AcquireNextImage2KHR);
+    INIT_PROC_EXT(KHR_swapchain, true, dev, GetDeviceGroupPresentCapabilitiesKHR);
+    INIT_PROC_EXT(KHR_swapchain, true, dev, GetDeviceGroupSurfacePresentModesKHR);
+    INIT_PROC_EXT(KHR_swapchain, true, dev, AcquireNextImage2KHR);
     INIT_PROC(false, dev, CmdDispatchBase);
     INIT_PROC(false, dev, CreateDescriptorUpdateTemplate);
     INIT_PROC(false, dev, DestroyDescriptorUpdateTemplate);
@@ -659,6 +659,8 @@
         "vkGetDrmDisplayEXT",
         "vkGetInstanceProcAddr",
         "vkGetPhysicalDeviceCalibrateableTimeDomainsEXT",
+        "vkGetPhysicalDeviceCalibrateableTimeDomainsKHR",
+        "vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR",
         "vkGetPhysicalDeviceDisplayPlaneProperties2KHR",
         "vkGetPhysicalDeviceDisplayProperties2KHR",
         "vkGetPhysicalDeviceExternalBufferProperties",
@@ -703,6 +705,7 @@
         "vkGetPhysicalDeviceToolProperties",
         "vkGetPhysicalDeviceToolPropertiesEXT",
         "vkGetPhysicalDeviceVideoCapabilitiesKHR",
+        "vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR",
         "vkGetPhysicalDeviceVideoFormatPropertiesKHR",
         "vkSubmitDebugUtilsMessageEXT",
     };
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 9e67725..00e987f 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -1473,23 +1473,14 @@
         image_format_properties.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
         image_format_properties.pNext = &ahb_usage;
 
-        if (instance_dispatch.GetPhysicalDeviceImageFormatProperties2) {
-            VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2(
-                pdev, &image_format_info, &image_format_properties);
-            if (result != VK_SUCCESS) {
-                ALOGE("VkGetPhysicalDeviceImageFormatProperties2 for AHB usage failed: %d", result);
-                return VK_ERROR_SURFACE_LOST_KHR;
-            }
-        }
-        else {
-            VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR(
-                pdev, &image_format_info,
-                &image_format_properties);
-            if (result != VK_SUCCESS) {
-                ALOGE("VkGetPhysicalDeviceImageFormatProperties2KHR for AHB usage failed: %d",
-                    result);
-                return VK_ERROR_SURFACE_LOST_KHR;
-            }
+        VkResult result = GetPhysicalDeviceImageFormatProperties2(
+            pdev, &image_format_info, &image_format_properties);
+        if (result != VK_SUCCESS) {
+            ALOGE(
+                "VkGetPhysicalDeviceImageFormatProperties2 for AHB usage "
+                "failed: %d",
+                result);
+            return VK_ERROR_SURFACE_LOST_KHR;
         }
 
         // Determine if USAGE_FRONT_BUFFER is needed.
diff --git a/vulkan/scripts/generator_common.py b/vulkan/scripts/generator_common.py
index 866c1b7..6b4cbad 100644
--- a/vulkan/scripts/generator_common.py
+++ b/vulkan/scripts/generator_common.py
@@ -351,9 +351,50 @@
                           'external', 'vulkan-headers', 'registry', 'vk.xml')
   tree = element_tree.parse(registry)
   root = tree.getroot()
+
+  for exts in root.iter('extensions'):
+    for extension in exts.iter('extension'):
+      if 'vulkan' not in extension.get('supported').split(','):
+        # ANDROID_native_buffer is a weird special case -- it's declared in vk.xml as
+        # disabled but we _do_ want to generate plumbing for it in the Android loader.
+        if extension.get('name') != 'VK_ANDROID_native_buffer':
+          print('skip extension disabled or not for vulkan: ' + extension.get('name'))
+          continue
+
+      apiversion = 'VK_VERSION_1_0'
+      if extension.tag == 'extension':
+        extname = extension.get('name')
+        if (extension.get('type') == 'instance' and
+            extension.get('promotedto') is not None):
+          promoted_inst_ext_dict[extname] = \
+              version_2_api_version(extension.get('promotedto'))
+        for req in extension.iter('require'):
+          if req.get('feature') is not None:
+            apiversion = req.get('feature')
+          for commands in req:
+            if commands.tag == 'command':
+              cmd_name = commands.get('name')
+              if cmd_name not in extension_dict:
+                extension_dict[cmd_name] = extname
+                version_dict[cmd_name] = apiversion
+
+  for feature in root.iter('feature'):
+    if 'vulkan' not in feature.get('api').split(','):
+      continue
+
+    apiversion = feature.get('name')
+    for req in feature.iter('require'):
+      for command in req:
+        if command.tag == 'command':
+          cmd_name = command.get('name')
+          version_dict[cmd_name] = apiversion
+
   for commands in root.iter('commands'):
     for command in commands:
       if command.tag == 'command':
+        if command.get('api') == 'vulkansc':
+          continue
+
         parameter_list = []
         protoset = False
         cmd_name = ''
@@ -361,12 +402,18 @@
         if command.get('alias') is not None:
           alias = command.get('alias')
           cmd_name = command.get('name')
-          alias_dict[cmd_name] = alias
-          command_list.append(cmd_name)
-          param_dict[cmd_name] = param_dict[alias].copy()
-          return_type_dict[cmd_name] = return_type_dict[alias]
+          # At this stage all valid commands have been added to the version
+          # dict so we can use it to filter valid commands
+          if cmd_name in version_dict:
+            alias_dict[cmd_name] = alias
+            command_list.append(cmd_name)
+            param_dict[cmd_name] = param_dict[alias].copy()
+            return_type_dict[cmd_name] = return_type_dict[alias]
         for params in command:
           if params.tag == 'param':
+            if params.get('api') == 'vulkansc':
+              # skip SC-only param variant
+              continue
             param_type = ''
             if params.text is not None and params.text.strip():
               param_type = params.text.strip() + ' '
@@ -387,39 +434,13 @@
                 cmd_type = c.text
               if c.tag == 'name':
                 cmd_name = c.text
-                protoset = True
-                command_list.append(cmd_name)
-                return_type_dict[cmd_name] = cmd_type
+                if cmd_name in version_dict:
+                  protoset = True
+                  command_list.append(cmd_name)
+                  return_type_dict[cmd_name] = cmd_type
         if protoset:
           param_dict[cmd_name] = parameter_list.copy()
 
-  for exts in root.iter('extensions'):
-    for extension in exts:
-      apiversion = 'VK_VERSION_1_0'
-      if extension.tag == 'extension':
-        extname = extension.get('name')
-        if (extension.get('type') == 'instance' and
-            extension.get('promotedto') is not None):
-          promoted_inst_ext_dict[extname] = \
-              version_2_api_version(extension.get('promotedto'))
-        for req in extension:
-          if req.get('feature') is not None:
-            apiversion = req.get('feature')
-          for commands in req:
-            if commands.tag == 'command':
-              cmd_name = commands.get('name')
-              if cmd_name not in extension_dict:
-                extension_dict[cmd_name] = extname
-                version_dict[cmd_name] = apiversion
-
-  for feature in root.iter('feature'):
-    apiversion = feature.get('name')
-    for req in feature:
-      for command in req:
-        if command.tag == 'command':
-          cmd_name = command.get('name')
-          if cmd_name in command_list:
-            version_dict[cmd_name] = apiversion
 
   version_code_set = set()
   for version in version_dict.values():
diff --git a/vulkan/scripts/null_generator.py b/vulkan/scripts/null_generator.py
index e9faef6..3624c1d 100644
--- a/vulkan/scripts/null_generator.py
+++ b/vulkan/scripts/null_generator.py
@@ -89,6 +89,8 @@
     f.write(gencom.copyright_and_warning(2015))
 
     f.write("""\
+#include <android/hardware_buffer.h>
+
 #include <algorithm>
 
 #include "null_driver_gen.h"