Merge "Update params doc" into sc-dev
diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp
index 37750fa..d7c07b9 100644
--- a/libs/gui/SurfaceControl.cpp
+++ b/libs/gui/SurfaceControl.cpp
@@ -166,7 +166,7 @@
     Mutex::Autolock _l(mLock);
     mWidth = width; mHeight = height;
     if (mBbq) {
-      mBbq->update(this, width, height, mFormat);
+        mBbq->update(mBbqChild, width, height, mFormat);
     }
 
 }
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 05bbc12..8059bc1 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -902,7 +902,16 @@
         if (layer->shadow.length > 0) {
             // This would require a new parameter/flag to SkShadowUtils::DrawShadow
             LOG_ALWAYS_FATAL_IF(layer->disableBlending, "Cannot disableBlending with a shadow");
-            drawShadow(canvas, bounds, layer->shadow);
+
+            // Technically, if bounds is a rect and roundRectClip is not empty,
+            // it means that the bounds and roundedCornersCrop were different
+            // enough that we should intersect them to find the proper shadow.
+            // In practice, this often happens when the two rectangles appear to
+            // not match due to rounding errors. Draw the rounded version, which
+            // looks more like the intent.
+            const auto& rrect =
+                    bounds.isRect() && !roundRectClip.isEmpty() ? roundRectClip : bounds;
+            drawShadow(canvas, rrect, layer->shadow);
             continue;
         }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 1ffb1c8..8c8331c 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -267,7 +267,7 @@
     auto& hwc = getCompositionEngine().getHwComposer();
     if (status_t result =
                 hwc.getDeviceCompositionChanges(*halDisplayId, anyLayersRequireClientComposition(),
-                                                &changes);
+                                                getState().earliestPresentTime, &changes);
         result != NO_ERROR) {
         ALOGE("chooseCompositionStrategy failed for %s: %d (%s)", getName().c_str(), result,
               strerror(-result));
@@ -367,13 +367,8 @@
         return fences;
     }
 
-    {
-        ATRACE_NAME("wait for earliest present time");
-        std::this_thread::sleep_until(getState().earliestPresentTime);
-    }
-
     auto& hwc = getCompositionEngine().getHwComposer();
-    hwc.presentAndGetReleaseFences(*halDisplayIdOpt);
+    hwc.presentAndGetReleaseFences(*halDisplayIdOpt, getState().earliestPresentTime);
 
     fences.presentFence = hwc.getPresentFence(*halDisplayIdOpt);
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 8a83639..e63d09a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -627,7 +627,7 @@
 TEST_F(DisplayChooseCompositionStrategyTest, takesEarlyOutOnHwcError) {
     EXPECT_CALL(*mDisplay, anyLayersRequireClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _))
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _))
             .WillOnce(Return(INVALID_OPERATION));
 
     mDisplay->chooseCompositionStrategy();
@@ -649,7 +649,8 @@
             .InSequence(s)
             .WillOnce(Return(false));
 
-    EXPECT_CALL(mHwComposer, getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _))
+    EXPECT_CALL(mHwComposer,
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _))
             .WillOnce(Return(NO_ERROR));
     EXPECT_CALL(*mDisplay, allLayersRequireClientComposition()).WillOnce(Return(false));
 
@@ -679,8 +680,9 @@
             .InSequence(s)
             .WillOnce(Return(false));
 
-    EXPECT_CALL(mHwComposer, getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _))
-            .WillOnce(DoAll(SetArgPointee<2>(changes), Return(NO_ERROR)));
+    EXPECT_CALL(mHwComposer,
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _))
+            .WillOnce(DoAll(SetArgPointee<3>(changes), Return(NO_ERROR)));
     EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(changes.changedTypes)).Times(1);
     EXPECT_CALL(*mDisplay, applyDisplayRequests(changes.displayRequests)).Times(1);
     EXPECT_CALL(*mDisplay, applyLayerRequestsToLayers(changes.layerRequests)).Times(1);
@@ -867,7 +869,8 @@
     sp<Fence> layer1Fence = new Fence();
     sp<Fence> layer2Fence = new Fence();
 
-    EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(HalDisplayId(DEFAULT_DISPLAY_ID))).Times(1);
+    EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(HalDisplayId(DEFAULT_DISPLAY_ID), _))
+            .Times(1);
     EXPECT_CALL(mHwComposer, getPresentFence(HalDisplayId(DEFAULT_DISPLAY_ID)))
             .WillOnce(Return(presentFence));
     EXPECT_CALL(mHwComposer,
@@ -1039,7 +1042,7 @@
 
     mDisplay->editState().isEnabled = true;
 
-    EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_));
+    EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _));
     EXPECT_CALL(*mDisplaySurface, onFrameCommitted());
 
     mDisplay->postFramebuffer();
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index bac894a..74d4b92 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -50,13 +50,14 @@
     MOCK_METHOD2(allocatePhysicalDisplay, void(hal::HWDisplayId, PhysicalDisplayId));
     MOCK_METHOD1(createLayer, HWC2::Layer*(HalDisplayId));
     MOCK_METHOD2(destroyLayer, void(HalDisplayId, HWC2::Layer*));
-    MOCK_METHOD3(getDeviceCompositionChanges,
-                 status_t(HalDisplayId, bool,
+    MOCK_METHOD4(getDeviceCompositionChanges,
+                 status_t(HalDisplayId, bool, std::chrono::steady_clock::time_point,
                           std::optional<android::HWComposer::DeviceRequestedChanges>*));
     MOCK_METHOD5(setClientTarget,
                  status_t(HalDisplayId, uint32_t, const sp<Fence>&, const sp<GraphicBuffer>&,
                           ui::Dataspace));
-    MOCK_METHOD1(presentAndGetReleaseFences, status_t(HalDisplayId));
+    MOCK_METHOD2(presentAndGetReleaseFences,
+                 status_t(HalDisplayId, std::chrono::steady_clock::time_point));
     MOCK_METHOD2(setPowerMode, status_t(PhysicalDisplayId, hal::PowerMode));
     MOCK_METHOD2(setActiveConfig, status_t(HalDisplayId, size_t));
     MOCK_METHOD2(setColorTransform, status_t(HalDisplayId, const mat4&));
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index b73d032..dc4839e 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -471,6 +471,7 @@
 
 status_t HWComposer::getDeviceCompositionChanges(
         HalDisplayId displayId, bool frameUsesClientComposition,
+        std::chrono::steady_clock::time_point earliestPresentTime,
         std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
     ATRACE_CALL();
 
@@ -487,12 +488,14 @@
 
     hal::Error error = hal::Error::NONE;
 
-    // First try to skip validate altogether when there is no client
-    // composition.  When there is client composition, since we haven't
-    // rendered to the client target yet, we should not attempt to skip
-    // validate.
+    // First try to skip validate altogether when we passed the earliest time
+    // to present and there is no client. Otherwise, we may present a frame too
+    // early or in case of client composition we first need to render the
+    // client target buffer.
+    const bool canSkipValidate =
+            std::chrono::steady_clock::now() >= earliestPresentTime && !frameUsesClientComposition;
     displayData.validateWasSkipped = false;
-    if (!frameUsesClientComposition) {
+    if (canSkipValidate) {
         sp<Fence> outPresentFence;
         uint32_t state = UINT32_MAX;
         error = hwcDisplay->presentOrValidate(&numTypes, &numRequests, &outPresentFence , &state);
@@ -556,7 +559,8 @@
     return fence->second;
 }
 
-status_t HWComposer::presentAndGetReleaseFences(HalDisplayId displayId) {
+status_t HWComposer::presentAndGetReleaseFences(
+        HalDisplayId displayId, std::chrono::steady_clock::time_point earliestPresentTime) {
     ATRACE_CALL();
 
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
@@ -572,6 +576,11 @@
         return NO_ERROR;
     }
 
+    {
+        ATRACE_NAME("wait for earliest present time");
+        std::this_thread::sleep_until(earliestPresentTime);
+    }
+
     auto error = hwcDisplay->present(&displayData.lastPresentFence);
     RETURN_IF_HWC_ERROR_FOR("present", error, displayId, UNKNOWN_ERROR);
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index f532e50..5bad529 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -129,13 +129,15 @@
     // expected.
     virtual status_t getDeviceCompositionChanges(
             HalDisplayId, bool frameUsesClientComposition,
+            std::chrono::steady_clock::time_point earliestPresentTime,
             std::optional<DeviceRequestedChanges>* outChanges) = 0;
 
     virtual status_t setClientTarget(HalDisplayId, uint32_t slot, const sp<Fence>& acquireFence,
                                      const sp<GraphicBuffer>& target, ui::Dataspace) = 0;
 
     // Present layers to the display and read releaseFences.
-    virtual status_t presentAndGetReleaseFences(HalDisplayId) = 0;
+    virtual status_t presentAndGetReleaseFences(
+            HalDisplayId, std::chrono::steady_clock::time_point earliestPresentTime) = 0;
 
     // set power mode
     virtual status_t setPowerMode(PhysicalDisplayId, hal::PowerMode) = 0;
@@ -268,13 +270,15 @@
 
     status_t getDeviceCompositionChanges(
             HalDisplayId, bool frameUsesClientComposition,
+            std::chrono::steady_clock::time_point earliestPresentTime,
             std::optional<DeviceRequestedChanges>* outChanges) override;
 
     status_t setClientTarget(HalDisplayId, uint32_t slot, const sp<Fence>& acquireFence,
                              const sp<GraphicBuffer>& target, ui::Dataspace) override;
 
     // Present layers to the display and read releaseFences.
-    status_t presentAndGetReleaseFences(HalDisplayId) override;
+    status_t presentAndGetReleaseFences(
+            HalDisplayId, std::chrono::steady_clock::time_point earliestPresentTime) override;
 
     // set power mode
     status_t setPowerMode(PhysicalDisplayId, hal::PowerMode mode) override;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 4fee723..8b7dc4d 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -221,7 +221,10 @@
 }
 
 void Layer::removeFromCurrentState() {
-    mRemovedFromCurrentState = true;
+    if (!mRemovedFromCurrentState) {
+        mRemovedFromCurrentState = true;
+        mFlinger->mScheduler->deregisterLayer(this);
+    }
 
     mFlinger->markLayerPendingRemovalLocked(this);
 }
@@ -246,7 +249,10 @@
 }
 
 void Layer::addToCurrentState() {
-    mRemovedFromCurrentState = false;
+    if (mRemovedFromCurrentState) {
+        mRemovedFromCurrentState = false;
+        mFlinger->mScheduler->registerLayer(this);
+    }
 
     for (const auto& child : mCurrentChildren) {
         child->addToCurrentState();
@@ -622,23 +628,6 @@
         return {};
     }
 
-    float casterCornerRadius = shadowLayer.geometry.roundedCornersRadius;
-    const FloatRect& cornerRadiusCropRect = shadowLayer.geometry.roundedCornersCrop;
-    const FloatRect& casterRect = shadowLayer.geometry.boundaries;
-
-    // crop used to set the corner radius may be larger than the content rect. Adjust the corner
-    // radius accordingly.
-    if (casterCornerRadius > 0.f) {
-        float cropRectOffset = std::max(std::abs(cornerRadiusCropRect.top - casterRect.top),
-                                        std::abs(cornerRadiusCropRect.left - casterRect.left));
-        if (cropRectOffset > casterCornerRadius) {
-            casterCornerRadius = 0;
-        } else {
-            casterCornerRadius -= cropRectOffset;
-        }
-        shadowLayer.geometry.roundedCornersRadius = casterCornerRadius;
-    }
-
     return shadowLayer;
 }
 
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 00090d9..653aca6 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -56,16 +56,14 @@
     noWorkNeeded,
     idleTimerWaiting,
     waitForQuietFrame,
-    waitForZeroPhase,
     waitForSamplePhase,
     sample
 };
 
-constexpr auto timeForRegionSampling = 5000000ns;
-constexpr auto maxRegionSamplingSkips = 10;
 constexpr auto defaultRegionSamplingWorkDuration = 3ms;
 constexpr auto defaultRegionSamplingPeriod = 100ms;
 constexpr auto defaultRegionSamplingTimerTimeout = 100ms;
+constexpr auto maxRegionSamplingDelay = 100ms;
 // TODO: (b/127403193) duration to string conversion could probably be constexpr
 template <typename Rep, typename Per>
 inline std::string toNsString(std::chrono::duration<Rep, Per> t) {
@@ -99,97 +97,22 @@
     }
 }
 
-struct SamplingOffsetCallback : VSyncSource::Callback {
-    SamplingOffsetCallback(RegionSamplingThread& samplingThread, Scheduler& scheduler,
-                           std::chrono::nanoseconds targetSamplingWorkDuration)
-          : mRegionSamplingThread(samplingThread),
-            mTargetSamplingWorkDuration(targetSamplingWorkDuration),
-            mVSyncSource(scheduler.makePrimaryDispSyncSource("SamplingThreadDispSyncListener", 0ns,
-                                                             0ns,
-                                                             /*traceVsync=*/false)) {
-        mVSyncSource->setCallback(this);
-    }
-
-    ~SamplingOffsetCallback() { stopVsyncListener(); }
-
-    SamplingOffsetCallback(const SamplingOffsetCallback&) = delete;
-    SamplingOffsetCallback& operator=(const SamplingOffsetCallback&) = delete;
-
-    void startVsyncListener() {
-        std::lock_guard lock(mMutex);
-        if (mVsyncListening) return;
-
-        mPhaseIntervalSetting = Phase::ZERO;
-        mVSyncSource->setVSyncEnabled(true);
-        mVsyncListening = true;
-    }
-
-    void stopVsyncListener() {
-        std::lock_guard lock(mMutex);
-        stopVsyncListenerLocked();
-    }
-
-private:
-    void stopVsyncListenerLocked() /*REQUIRES(mMutex)*/ {
-        if (!mVsyncListening) return;
-
-        mVSyncSource->setVSyncEnabled(false);
-        mVsyncListening = false;
-    }
-
-    void onVSyncEvent(nsecs_t /*when*/, nsecs_t /*expectedVSyncTimestamp*/,
-                      nsecs_t /*deadlineTimestamp*/) final {
-        std::unique_lock<decltype(mMutex)> lock(mMutex);
-
-        if (mPhaseIntervalSetting == Phase::ZERO) {
-            ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForSamplePhase));
-            mPhaseIntervalSetting = Phase::SAMPLING;
-            mVSyncSource->setDuration(mTargetSamplingWorkDuration, 0ns);
-            return;
-        }
-
-        if (mPhaseIntervalSetting == Phase::SAMPLING) {
-            mPhaseIntervalSetting = Phase::ZERO;
-            mVSyncSource->setDuration(0ns, 0ns);
-            stopVsyncListenerLocked();
-            lock.unlock();
-            mRegionSamplingThread.notifySamplingOffset();
-            return;
-        }
-    }
-
-    RegionSamplingThread& mRegionSamplingThread;
-    const std::chrono::nanoseconds mTargetSamplingWorkDuration;
-    mutable std::mutex mMutex;
-    enum class Phase {
-        ZERO,
-        SAMPLING
-    } mPhaseIntervalSetting /*GUARDED_BY(mMutex) macro doesnt work with unique_lock?*/
-            = Phase::ZERO;
-    bool mVsyncListening /*GUARDED_BY(mMutex)*/ = false;
-    std::unique_ptr<VSyncSource> mVSyncSource;
-};
-
-RegionSamplingThread::RegionSamplingThread(SurfaceFlinger& flinger, Scheduler& scheduler,
-                                           const TimingTunables& tunables)
+RegionSamplingThread::RegionSamplingThread(SurfaceFlinger& flinger, const TimingTunables& tunables)
       : mFlinger(flinger),
-        mScheduler(scheduler),
         mTunables(tunables),
         mIdleTimer(
                 "RegSampIdle",
                 std::chrono::duration_cast<std::chrono::milliseconds>(
                         mTunables.mSamplingTimerTimeout),
                 [] {}, [this] { checkForStaleLuma(); }),
-        mPhaseCallback(std::make_unique<SamplingOffsetCallback>(*this, mScheduler,
-                                                                tunables.mSamplingDuration)),
-        lastSampleTime(0ns) {
+        mLastSampleTime(0ns) {
     mThread = std::thread([this]() { threadMain(); });
     pthread_setname_np(mThread.native_handle(), "RegionSampling");
     mIdleTimer.start();
 }
 
-RegionSamplingThread::RegionSamplingThread(SurfaceFlinger& flinger, Scheduler& scheduler)
-      : RegionSamplingThread(flinger, scheduler,
+RegionSamplingThread::RegionSamplingThread(SurfaceFlinger& flinger)
+      : RegionSamplingThread(flinger,
                              TimingTunables{defaultRegionSamplingWorkDuration,
                                             defaultRegionSamplingPeriod,
                                             defaultRegionSamplingTimerTimeout}) {}
@@ -224,48 +147,46 @@
 void RegionSamplingThread::checkForStaleLuma() {
     std::lock_guard lock(mThreadControlMutex);
 
-    if (mDiscardedFrames > 0) {
-        ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForZeroPhase));
-        mDiscardedFrames = 0;
-        mPhaseCallback->startVsyncListener();
+    if (mSampleRequestTime.has_value()) {
+        ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForSamplePhase));
+        mSampleRequestTime.reset();
+        mFlinger.scheduleRegionSamplingThread();
     }
 }
 
-void RegionSamplingThread::notifyNewContent() {
-    doSample();
+void RegionSamplingThread::onCompositionComplete(
+        std::optional<std::chrono::steady_clock::time_point> samplingDeadline) {
+    doSample(samplingDeadline);
 }
 
-void RegionSamplingThread::notifySamplingOffset() {
-    doSample();
-}
-
-void RegionSamplingThread::doSample() {
+void RegionSamplingThread::doSample(
+        std::optional<std::chrono::steady_clock::time_point> samplingDeadline) {
     std::lock_guard lock(mThreadControlMutex);
-    auto now = std::chrono::nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC));
-    if (lastSampleTime + mTunables.mSamplingPeriod > now) {
+    const auto now = std::chrono::steady_clock::now();
+    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));
-        if (mDiscardedFrames == 0) mDiscardedFrames++;
+        mSampleRequestTime = now;
         return;
     }
-    if (mDiscardedFrames < maxRegionSamplingSkips) {
+    if (!mSampleRequestTime.has_value() || now - *mSampleRequestTime < maxRegionSamplingDelay) {
         // If there is relatively little time left for surfaceflinger
         // until the next vsync deadline, defer this sampling work
         // to a later frame, when hopefully there will be more time.
-        const DisplayStatInfo stats = mScheduler.getDisplayStatInfo(systemTime());
-        if (std::chrono::nanoseconds(stats.vsyncTime) - now < timeForRegionSampling) {
+        if (samplingDeadline.has_value() && now + mTunables.mSamplingDuration > *samplingDeadline) {
             ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForQuietFrame));
-            mDiscardedFrames++;
+            mSampleRequestTime = mSampleRequestTime.value_or(now);
             return;
         }
     }
 
     ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::sample));
 
-    mDiscardedFrames = 0;
-    lastSampleTime = now;
+    mSampleRequestTime.reset();
+    mLastSampleTime = now;
 
     mIdleTimer.reset();
-    mPhaseCallback->stopVsyncListener();
 
     mSampleRequested = true;
     mCondition.notify_one();
diff --git a/services/surfaceflinger/RegionSamplingThread.h b/services/surfaceflinger/RegionSamplingThread.h
index 86632db..2231853 100644
--- a/services/surfaceflinger/RegionSamplingThread.h
+++ b/services/surfaceflinger/RegionSamplingThread.h
@@ -63,9 +63,8 @@
     struct EnvironmentTimingTunables : TimingTunables {
         EnvironmentTimingTunables();
     };
-    explicit RegionSamplingThread(SurfaceFlinger& flinger, Scheduler& scheduler,
-                                  const TimingTunables& tunables);
-    explicit RegionSamplingThread(SurfaceFlinger& flinger, Scheduler& scheduler);
+    explicit RegionSamplingThread(SurfaceFlinger& flinger, const TimingTunables& tunables);
+    explicit RegionSamplingThread(SurfaceFlinger& flinger);
 
     ~RegionSamplingThread();
 
@@ -76,12 +75,11 @@
     // Remove the listener to stop receiving median luma notifications.
     void removeListener(const sp<IRegionSamplingListener>& listener);
 
-    // Notifies sampling engine that new content is available. This will trigger a sampling
-    // pass at some point in the future.
-    void notifyNewContent();
-
-    // Notifies the sampling engine that it has a good timing window in which to sample.
-    void notifySamplingOffset();
+    // Notifies sampling engine that composition is done and new content is
+    // available, and the deadline for the sampling work on the main thread to
+    // be completed without eating the budget of another frame.
+    void onCompositionComplete(
+            std::optional<std::chrono::steady_clock::time_point> samplingDeadline);
 
 private:
     struct Descriptor {
@@ -99,7 +97,7 @@
             const sp<GraphicBuffer>& buffer, const Point& leftTop,
             const std::vector<RegionSamplingThread::Descriptor>& descriptors, uint32_t orientation);
 
-    void doSample();
+    void doSample(std::optional<std::chrono::steady_clock::time_point> samplingDeadline);
     void binderDied(const wp<IBinder>& who) override;
     void checkForStaleLuma();
 
@@ -107,20 +105,18 @@
     void threadMain();
 
     SurfaceFlinger& mFlinger;
-    Scheduler& mScheduler;
     const TimingTunables mTunables;
     scheduler::OneShotTimer mIdleTimer;
 
-    std::unique_ptr<SamplingOffsetCallback> const mPhaseCallback;
-
     std::thread mThread;
 
     std::mutex mThreadControlMutex;
     std::condition_variable_any mCondition;
     bool mRunning GUARDED_BY(mThreadControlMutex) = true;
     bool mSampleRequested GUARDED_BY(mThreadControlMutex) = false;
-    uint32_t mDiscardedFrames GUARDED_BY(mThreadControlMutex) = 0;
-    std::chrono::nanoseconds lastSampleTime GUARDED_BY(mThreadControlMutex);
+    std::optional<std::chrono::steady_clock::time_point> mSampleRequestTime
+            GUARDED_BY(mThreadControlMutex);
+    std::chrono::steady_clock::time_point mLastSampleTime GUARDED_BY(mThreadControlMutex);
 
     std::mutex mSamplingMutex;
     std::unordered_map<wp<IBinder>, Descriptor, WpHash> mDescriptors GUARDED_BY(mSamplingMutex);
diff --git a/services/surfaceflinger/Scheduler/DispSyncSource.cpp b/services/surfaceflinger/Scheduler/DispSyncSource.cpp
index ce5c31a..50b38c9 100644
--- a/services/surfaceflinger/Scheduler/DispSyncSource.cpp
+++ b/services/surfaceflinger/Scheduler/DispSyncSource.cpp
@@ -60,8 +60,7 @@
                 mRegistration.schedule({.workDuration = mWorkDuration.count(),
                                         .readyDuration = mReadyDuration.count(),
                                         .earliestVsync = mLastCallTime.count()});
-        LOG_ALWAYS_FATAL_IF((scheduleResult != scheduler::ScheduleResult::Scheduled),
-                            "Error scheduling callback: rc %X", scheduleResult);
+        LOG_ALWAYS_FATAL_IF((!scheduleResult.has_value()), "Error scheduling callback");
     }
 
     void stop() {
@@ -100,8 +99,7 @@
                     mRegistration.schedule({.workDuration = mWorkDuration.count(),
                                             .readyDuration = mReadyDuration.count(),
                                             .earliestVsync = vsyncTime});
-            LOG_ALWAYS_FATAL_IF((scheduleResult != ScheduleResult::Scheduled),
-                                "Error rescheduling callback: rc %X", scheduleResult);
+            LOG_ALWAYS_FATAL_IF(!scheduleResult.has_value(), "Error rescheduling callback");
         }
     }
 
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index f4bc2a1..0563795 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -84,8 +84,11 @@
 LayerHistory::~LayerHistory() = default;
 
 void LayerHistory::registerLayer(Layer* layer, LayerVoteType type) {
-    auto info = std::make_unique<LayerInfo>(layer->getName(), layer->getOwnerUid(), type);
     std::lock_guard lock(mLock);
+    for (const auto& info : mLayerInfos) {
+        LOG_ALWAYS_FATAL_IF(info.first == layer, "%s already registered", layer->getName().c_str());
+    }
+    auto info = std::make_unique<LayerInfo>(layer->getName(), layer->getOwnerUid(), type);
     mLayerInfos.emplace_back(layer, std::move(info));
 }
 
@@ -94,7 +97,7 @@
 
     const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
                                  [layer](const auto& pair) { return pair.first == layer; });
-    LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
+    LOG_ALWAYS_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
 
     const size_t i = static_cast<size_t>(it - mLayerInfos.begin());
     if (i < mActiveLayersEnd) {
@@ -111,7 +114,11 @@
 
     const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
                                  [layer](const auto& pair) { return pair.first == layer; });
-    LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
+    if (it == mLayerInfos.end()) {
+        // Offscreen layer
+        ALOGV("LayerHistory::record: %s not registered", layer->getName().c_str());
+        return;
+    }
 
     const auto& info = it->second;
     const auto layerProps = LayerInfo::LayerProps{
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index 7ff0ddf..4d51125 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -23,7 +23,6 @@
 #include <utils/threads.h>
 
 #include <gui/DisplayEventReceiver.h>
-#include <gui/IDisplayEventConnection.h>
 
 #include "EventThread.h"
 #include "FrameTimeline.h"
@@ -33,27 +32,32 @@
 namespace android::impl {
 
 void MessageQueue::Handler::dispatchRefresh() {
-    if ((android_atomic_or(eventMaskRefresh, &mEventMask) & eventMaskRefresh) == 0) {
+    if ((mEventMask.fetch_or(eventMaskRefresh) & eventMaskRefresh) == 0) {
         mQueue.mLooper->sendMessage(this, Message(MessageQueue::REFRESH));
     }
 }
 
 void MessageQueue::Handler::dispatchInvalidate(int64_t vsyncId, nsecs_t expectedVSyncTimestamp) {
-    if ((android_atomic_or(eventMaskInvalidate, &mEventMask) & eventMaskInvalidate) == 0) {
+    if ((mEventMask.fetch_or(eventMaskInvalidate) & eventMaskInvalidate) == 0) {
         mVsyncId = vsyncId;
         mExpectedVSyncTime = expectedVSyncTimestamp;
         mQueue.mLooper->sendMessage(this, Message(MessageQueue::INVALIDATE));
     }
 }
 
+bool MessageQueue::Handler::invalidatePending() {
+    constexpr auto pendingMask = eventMaskInvalidate | eventMaskRefresh;
+    return (mEventMask.load() & pendingMask) != 0;
+}
+
 void MessageQueue::Handler::handleMessage(const Message& message) {
     switch (message.what) {
         case INVALIDATE:
-            android_atomic_and(~eventMaskInvalidate, &mEventMask);
+            mEventMask.fetch_and(~eventMaskInvalidate);
             mQueue.mFlinger->onMessageReceived(message.what, mVsyncId, mExpectedVSyncTime);
             break;
         case REFRESH:
-            android_atomic_and(~eventMaskRefresh, &mEventMask);
+            mEventMask.fetch_and(~eventMaskRefresh);
             mQueue.mFlinger->onMessageReceived(message.what, mVsyncId, mExpectedVSyncTime);
             break;
     }
@@ -106,7 +110,7 @@
     {
         std::lock_guard lock(mVsync.mutex);
         mVsync.lastCallbackTime = std::chrono::nanoseconds(vsyncTime);
-        mVsync.mScheduled = false;
+        mVsync.scheduled = false;
     }
     mHandler->dispatchInvalidate(mVsync.tokenManager->generateTokenForPredictions(
                                          {targetWakeupTime, readyTime, vsyncTime}),
@@ -131,9 +135,10 @@
     ATRACE_CALL();
     std::lock_guard lock(mVsync.mutex);
     mVsync.workDuration = workDuration;
-    if (mVsync.mScheduled) {
-        mVsync.registration->schedule({mVsync.workDuration.get().count(), /*readyDuration=*/0,
-                                       mVsync.lastCallbackTime.count()});
+    if (mVsync.scheduled) {
+        mVsync.expectedWakeupTime = mVsync.registration->schedule(
+                {mVsync.workDuration.get().count(),
+                 /*readyDuration=*/0, mVsync.lastCallbackTime.count()});
     }
 }
 
@@ -176,10 +181,11 @@
     }
 
     std::lock_guard lock(mVsync.mutex);
-    mVsync.mScheduled = true;
-    mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
-                                   .readyDuration = 0,
-                                   .earliestVsync = mVsync.lastCallbackTime.count()});
+    mVsync.scheduled = true;
+    mVsync.expectedWakeupTime =
+            mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
+                                           .readyDuration = 0,
+                                           .earliestVsync = mVsync.lastCallbackTime.count()});
 }
 
 void MessageQueue::refresh() {
@@ -200,4 +206,19 @@
     }
 }
 
+std::optional<std::chrono::steady_clock::time_point> MessageQueue::nextExpectedInvalidate() {
+    if (mHandler->invalidatePending()) {
+        return std::chrono::steady_clock::now();
+    }
+
+    std::lock_guard lock(mVsync.mutex);
+    if (mVsync.scheduled) {
+        LOG_ALWAYS_FATAL_IF(!mVsync.expectedWakeupTime.has_value(), "callback was never scheduled");
+        const auto expectedWakeupTime = std::chrono::nanoseconds(*mVsync.expectedWakeupTime);
+        return std::optional<std::chrono::steady_clock::time_point>(expectedWakeupTime);
+    }
+
+    return std::nullopt;
+}
+
 } // namespace android::impl
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index 2934af0..58ce9b9 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -72,6 +72,7 @@
     virtual void postMessage(sp<MessageHandler>&&) = 0;
     virtual void invalidate() = 0;
     virtual void refresh() = 0;
+    virtual std::optional<std::chrono::steady_clock::time_point> nextExpectedInvalidate() = 0;
 };
 
 // ---------------------------------------------------------------------------
@@ -81,9 +82,13 @@
 class MessageQueue : public android::MessageQueue {
 protected:
     class Handler : public MessageHandler {
-        enum { eventMaskInvalidate = 0x1, eventMaskRefresh = 0x2, eventMaskTransaction = 0x4 };
+        enum : uint32_t {
+            eventMaskInvalidate = 0x1,
+            eventMaskRefresh = 0x2,
+            eventMaskTransaction = 0x4
+        };
         MessageQueue& mQueue;
-        int32_t mEventMask;
+        std::atomic<uint32_t> mEventMask;
         std::atomic<int64_t> mVsyncId;
         std::atomic<nsecs_t> mExpectedVSyncTime;
 
@@ -92,6 +97,7 @@
         void handleMessage(const Message& message) override;
         virtual void dispatchRefresh();
         virtual void dispatchInvalidate(int64_t vsyncId, nsecs_t expectedVSyncTimestamp);
+        virtual bool invalidatePending();
     };
 
     friend class Handler;
@@ -107,7 +113,8 @@
         TracedOrdinal<std::chrono::nanoseconds> workDuration
                 GUARDED_BY(mutex) = {"VsyncWorkDuration-sf", std::chrono::nanoseconds(0)};
         std::chrono::nanoseconds lastCallbackTime GUARDED_BY(mutex) = std::chrono::nanoseconds{0};
-        bool mScheduled GUARDED_BY(mutex) = false;
+        bool scheduled GUARDED_BY(mutex) = false;
+        std::optional<nsecs_t> expectedWakeupTime GUARDED_BY(mutex);
         TracedOrdinal<int> value = {"VSYNC-sf", 0};
     };
 
@@ -141,6 +148,8 @@
 
     // sends REFRESH message at next VSYNC
     void refresh() override;
+
+    std::optional<std::chrono::steady_clock::time_point> nextExpectedInvalidate() override;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h
index 9d71103..b52706f 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatch.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h
@@ -16,8 +16,10 @@
 
 #pragma once
 
+#include <utils/Log.h>
 #include <utils/Timers.h>
 #include <functional>
+#include <optional>
 #include <string>
 
 #include "StrongTyping.h"
@@ -26,7 +28,8 @@
 class TimeKeeper;
 class VSyncTracker;
 
-enum class ScheduleResult { Scheduled, CannotSchedule, Error };
+using ScheduleResult = std::optional<nsecs_t>;
+
 enum class CancelResult { Cancelled, TooLate, Error };
 
 /*
@@ -121,11 +124,8 @@
      *
      * \param [in] token           The callback to schedule.
      * \param [in] scheduleTiming  The timing information for this schedule call
-     * \return                     A ScheduleResult::Scheduled if callback was scheduled.
-     *                             A ScheduleResult::CannotSchedule
-     *                             if (workDuration + readyDuration - earliestVsync) is in the past,
-     * or if a callback was dispatched for the predictedVsync already. A ScheduleResult::Error if
-     * there was another error.
+     * \return                     The expected callback time if a callback was scheduled.
+     *                             std::nullopt if the callback is not registered.
      */
     virtual ScheduleResult schedule(CallbackToken token, ScheduleTiming scheduleTiming) = 0;
 
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index ca6ea27..28be962 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -26,6 +26,20 @@
 namespace android::scheduler {
 using base::StringAppendF;
 
+namespace {
+nsecs_t getExpectedCallbackTime(nsecs_t nextVsyncTime,
+                                const VSyncDispatch::ScheduleTiming& timing) {
+    return nextVsyncTime - timing.readyDuration - timing.workDuration;
+}
+
+nsecs_t getExpectedCallbackTime(VSyncTracker& tracker, nsecs_t now,
+                                const VSyncDispatch::ScheduleTiming& timing) {
+    const auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
+            std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
+    return getExpectedCallbackTime(nextVsyncTime, timing);
+}
+} // namespace
+
 VSyncDispatch::~VSyncDispatch() = default;
 VSyncTracker::~VSyncTracker() = default;
 TimeKeeper::~TimeKeeper() = default;
@@ -74,7 +88,7 @@
     bool const wouldSkipAVsyncTarget =
             mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance));
     if (wouldSkipAVsyncTarget) {
-        return ScheduleResult::Scheduled;
+        return getExpectedCallbackTime(nextVsyncTime, timing);
     }
 
     bool const alreadyDispatchedForVsync = mLastDispatchTime &&
@@ -89,7 +103,7 @@
     auto const nextReadyTime = nextVsyncTime - timing.readyDuration;
     mScheduleTiming = timing;
     mArmedInfo = {nextWakeupTime, nextVsyncTime, nextReadyTime};
-    return ScheduleResult::Scheduled;
+    return getExpectedCallbackTime(nextVsyncTime, timing);
 }
 
 void VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate(VSyncDispatch::ScheduleTiming timing) {
@@ -317,7 +331,7 @@
 
 ScheduleResult VSyncDispatchTimerQueue::schedule(CallbackToken token,
                                                  ScheduleTiming scheduleTiming) {
-    auto result = ScheduleResult::Error;
+    ScheduleResult result;
     {
         std::lock_guard lock(mMutex);
 
@@ -333,11 +347,11 @@
         auto const rearmImminent = now > mIntendedWakeupTime;
         if (CC_UNLIKELY(rearmImminent)) {
             callback->addPendingWorkloadUpdate(scheduleTiming);
-            return ScheduleResult::Scheduled;
+            return getExpectedCallbackTime(mTracker, now, scheduleTiming);
         }
 
         result = callback->schedule(scheduleTiming, mTracker, now);
-        if (result == ScheduleResult::CannotSchedule) {
+        if (!result.has_value()) {
             return result;
         }
 
@@ -416,7 +430,7 @@
 
 ScheduleResult VSyncCallbackRegistration::schedule(VSyncDispatch::ScheduleTiming scheduleTiming) {
     if (!mValidToken) {
-        return ScheduleResult::Error;
+        return std::nullopt;
     }
     return mDispatch.get().schedule(mToken, scheduleTiming);
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 4a007ff..a5b7107 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1720,11 +1720,11 @@
 }
 
 SurfaceFlinger::FenceWithFenceTime SurfaceFlinger::previousFrameFence() {
-    // We are storing the last 2 present fences. If sf's phase offset is to be
-    // woken up before the actual vsync but targeting the next vsync, we need to check
-    // fence N-2
-    return mVsyncModulator->getVsyncConfig().sfOffset > 0 ? mPreviousPresentFences[0]
-                                                          : mPreviousPresentFences[1];
+    const auto now = systemTime();
+    const auto vsyncPeriod = mScheduler->getDisplayStatInfo(now).vsyncPeriod;
+    const bool expectedPresentTimeIsTheNextVsync = mExpectedPresentTime - now <= vsyncPeriod;
+    return expectedPresentTimeIsTheNextVsync ? mPreviousPresentFences[0]
+                                             : mPreviousPresentFences[1];
 }
 
 bool SurfaceFlinger::previousFramePending(int graceTimeMs) {
@@ -1920,6 +1920,7 @@
         mRefreshPending = true;
         onMessageRefresh();
     }
+    notifyRegionSamplingThread();
 }
 
 bool SurfaceFlinger::handleMessageTransaction() {
@@ -2312,10 +2313,6 @@
         }
     }
 
-    if (mLumaSampling && mRegionSamplingThread) {
-        mRegionSamplingThread->notifyNewContent();
-    }
-
     // Even though ATRACE_INT64 already checks if tracing is enabled, it doesn't prevent the
     // side-effect of getTotalSize(), so we check that again here
     if (ATRACE_ENABLED()) {
@@ -3068,8 +3065,7 @@
                            configs.late.sfWorkDuration);
 
     mRegionSamplingThread =
-            new RegionSamplingThread(*this, *mScheduler,
-                                     RegionSamplingThread::EnvironmentTimingTunables());
+            new RegionSamplingThread(*this, RegionSamplingThread::EnvironmentTimingTunables());
     mFpsReporter = new FpsReporter(*mFrameTimeline, *this);
     // Dispatch a mode change request for the primary display on scheduler
     // initialization, so that the EventThreads always contain a reference to a
@@ -6450,13 +6446,17 @@
 
 void SurfaceFlinger::onLayerFirstRef(Layer* layer) {
     mNumLayers++;
-    mScheduler->registerLayer(layer);
+    if (!layer->isRemovedFromCurrentState()) {
+        mScheduler->registerLayer(layer);
+    }
 }
 
 void SurfaceFlinger::onLayerDestroyed(Layer* layer) {
-    mScheduler->deregisterLayer(layer);
     mNumLayers--;
     removeFromOffscreenLayers(layer);
+    if (!layer->isRemovedFromCurrentState()) {
+        mScheduler->deregisterLayer(layer);
+    }
 }
 
 // WARNING: ONLY CALL THIS FROM LAYER DTOR
@@ -6765,6 +6765,19 @@
 
     return layer;
 }
+
+void SurfaceFlinger::scheduleRegionSamplingThread() {
+    static_cast<void>(schedule([&] { notifyRegionSamplingThread(); }));
+}
+
+void SurfaceFlinger::notifyRegionSamplingThread() {
+    if (!mLumaSampling || !mRegionSamplingThread) {
+        return;
+    }
+
+    mRegionSamplingThread->onCompositionComplete(mEventQueue->nextExpectedInvalidate());
+}
+
 } // namespace android
 
 #if defined(__gl_h_)
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index da874b6..cb8d312 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1433,6 +1433,9 @@
             REQUIRES(mStateLock);
 
     std::atomic<ui::Transform::RotationFlags> mDefaultDisplayTransformHint;
+
+    void scheduleRegionSamplingThread();
+    void notifyRegionSamplingThread();
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 10d58a6..d6a0787 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -351,12 +351,16 @@
             std::max(mTimeStats.displayEventConnectionsCountLegacy, count);
 }
 
+static int32_t toMs(nsecs_t nanos) {
+    int64_t millis =
+            std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::nanoseconds(nanos))
+                    .count();
+    millis = std::clamp(millis, int64_t(INT32_MIN), int64_t(INT32_MAX));
+    return static_cast<int32_t>(millis);
+}
+
 static int32_t msBetween(nsecs_t start, nsecs_t end) {
-    int64_t delta = std::chrono::duration_cast<std::chrono::milliseconds>(
-                            std::chrono::nanoseconds(end - start))
-                            .count();
-    delta = std::clamp(delta, int64_t(INT32_MIN), int64_t(INT32_MAX));
-    return static_cast<int32_t>(delta);
+    return toMs(end - start);
 }
 
 void TimeStats::recordFrameDuration(nsecs_t startTime, nsecs_t endTime) {
@@ -829,10 +833,9 @@
         // TimeStats Histograms only retain positive values, so we don't need to check if these
         // deadlines were really missed if we know that the frame had jank, since deadlines
         // that were met will be dropped.
-        timelineStats.displayDeadlineDeltas.insert(static_cast<int32_t>(info.displayDeadlineDelta));
-        timelineStats.displayPresentDeltas.insert(static_cast<int32_t>(info.displayPresentJitter));
-        timeStatsLayer.deltas["appDeadlineDeltas"].insert(
-                static_cast<int32_t>(info.appDeadlineDelta));
+        timelineStats.displayDeadlineDeltas.insert(toMs(info.displayDeadlineDelta));
+        timelineStats.displayPresentDeltas.insert(toMs(info.displayPresentJitter));
+        timeStatsLayer.deltas["appDeadlineDeltas"].insert(toMs(info.appDeadlineDelta));
     }
 }
 
diff --git a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
index 2828d61..43d957c 100644
--- a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
@@ -273,6 +273,198 @@
     }
 }
 
+TEST_P(LayerTypeAndRenderTypeTransactionTest, SetCornerRadiusBufferRotationTransform) {
+    sp<SurfaceControl> layer;
+    sp<SurfaceControl> parent;
+    ASSERT_NO_FATAL_FAILURE(
+            parent = LayerTransactionTest::createLayer("parent", 0, 0,
+                                                       ISurfaceComposerClient::eFXSurfaceEffect));
+
+    const uint32_t bufferWidth = 1500;
+    const uint32_t bufferHeight = 300;
+
+    const uint32_t layerWidth = 300;
+    const uint32_t layerHeight = 1500;
+
+    const uint32_t testArea = 4;
+    const float cornerRadius = 120.0f;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", bufferWidth, bufferHeight));
+    ASSERT_NO_FATAL_FAILURE(fillLayerColor(layer, Color::RED, bufferWidth, bufferHeight));
+
+    Transaction()
+            .reparent(layer, parent)
+            .setColor(parent, half3(0, 1, 0))
+            .setCrop(parent, Rect(0, 0, layerWidth, layerHeight))
+            .setCornerRadius(parent, cornerRadius)
+
+            .setTransform(layer, ui::Transform::ROT_90)
+            .setDestinationFrame(layer, Rect(0, 0, layerWidth, layerHeight))
+            .apply();
+    {
+        auto shot = getScreenCapture();
+        // Corners are transparent
+        // top-left
+        shot->expectColor(Rect(0, 0, testArea, testArea), Color::BLACK);
+        // top-right
+        shot->expectColor(Rect(layerWidth - testArea, 0, layerWidth, testArea), Color::BLACK);
+        // bottom-left
+        shot->expectColor(Rect(0, layerHeight - testArea, testArea, layerHeight), Color::BLACK);
+        // bottom-right
+        shot->expectColor(Rect(layerWidth - testArea, layerHeight - testArea, layerWidth,
+                               layerHeight),
+                          Color::BLACK);
+
+        // Area after corner radius is solid
+        // top-left to top-right under the corner
+        shot->expectColor(Rect(0, cornerRadius, layerWidth, cornerRadius + testArea), Color::RED);
+        // bottom-left to bottom-right above the corner
+        shot->expectColor(Rect(0, layerHeight - cornerRadius - testArea, layerWidth,
+                               layerHeight - cornerRadius),
+                          Color::RED);
+        // left side after the corner
+        shot->expectColor(Rect(cornerRadius, 0, cornerRadius + testArea, layerHeight), Color::RED);
+        // right side before the corner
+        shot->expectColor(Rect(layerWidth - cornerRadius - testArea, 0, layerWidth - cornerRadius,
+                               layerHeight),
+                          Color::RED);
+    }
+}
+
+TEST_P(LayerTypeAndRenderTypeTransactionTest, SetCornerRadiusBufferCropTransform) {
+    sp<SurfaceControl> layer;
+    sp<SurfaceControl> parent;
+    ASSERT_NO_FATAL_FAILURE(
+            parent = LayerTransactionTest::createLayer("parent", 0, 0,
+                                                       ISurfaceComposerClient::eFXSurfaceEffect));
+
+    const uint32_t bufferWidth = 150 * 2;
+    const uint32_t bufferHeight = 750 * 2;
+
+    const Rect bufferCrop(0, 0, 150, 750);
+
+    const uint32_t layerWidth = 300;
+    const uint32_t layerHeight = 1500;
+
+    const uint32_t testArea = 4;
+    const float cornerRadius = 120.0f;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", bufferWidth, bufferHeight));
+    ASSERT_NO_FATAL_FAILURE(fillLayerQuadrant(layer, bufferWidth, bufferHeight, Color::RED,
+                                              Color::BLACK, Color::GREEN, Color::BLUE));
+
+    Transaction()
+            .reparent(layer, parent)
+            .setColor(parent, half3(0, 1, 0))
+            .setCrop(parent, Rect(0, 0, layerWidth, layerHeight))
+            .setCornerRadius(parent, cornerRadius)
+
+            .setBufferCrop(layer, bufferCrop)
+            .setDestinationFrame(layer, Rect(0, 0, layerWidth, layerHeight))
+            .apply();
+    {
+        auto shot = getScreenCapture();
+        // Corners are transparent
+        // top-left
+        shot->expectColor(Rect(0, 0, testArea, testArea), Color::BLACK);
+        // top-right
+        shot->expectColor(Rect(layerWidth - testArea, 0, layerWidth, testArea), Color::BLACK);
+        // bottom-left
+        shot->expectColor(Rect(0, layerHeight - testArea, testArea, layerHeight), Color::BLACK);
+        // bottom-right
+        shot->expectColor(Rect(layerWidth - testArea, layerHeight - testArea, layerWidth,
+                               layerHeight),
+                          Color::BLACK);
+
+        // Area after corner radius is solid
+        // since the buffer is scaled, there will blending so adjust some of the bounds when
+        // checking.
+        float adjustedCornerRadius = cornerRadius + 15;
+        float adjustedLayerHeight = layerHeight - 15;
+        float adjustedLayerWidth = layerWidth - 15;
+
+        // top-left to top-right under the corner
+        shot->expectColor(Rect(15, adjustedCornerRadius, adjustedLayerWidth,
+                               adjustedCornerRadius + testArea),
+                          Color::RED);
+        // bottom-left to bottom-right above the corner
+        shot->expectColor(Rect(15, adjustedLayerHeight - adjustedCornerRadius - testArea,
+                               adjustedLayerWidth, adjustedLayerHeight - adjustedCornerRadius),
+                          Color::RED);
+        // left side after the corner
+        shot->expectColor(Rect(adjustedCornerRadius, 15, adjustedCornerRadius + testArea,
+                               adjustedLayerHeight),
+                          Color::RED);
+        // right side before the corner
+        shot->expectColor(Rect(adjustedLayerWidth - adjustedCornerRadius - testArea, 15,
+                               adjustedLayerWidth - adjustedCornerRadius, adjustedLayerHeight),
+                          Color::RED);
+    }
+}
+
+TEST_P(LayerTypeAndRenderTypeTransactionTest, SetCornerRadiusChildBufferRotationTransform) {
+    sp<SurfaceControl> layer;
+    sp<SurfaceControl> parent;
+    ASSERT_NO_FATAL_FAILURE(
+            parent = LayerTransactionTest::createLayer("parent", 0, 0,
+                                                       ISurfaceComposerClient::eFXSurfaceEffect));
+
+    const uint32_t bufferWidth = 1500;
+    const uint32_t bufferHeight = 300;
+
+    const uint32_t layerWidth = 300;
+    const uint32_t layerHeight = 1500;
+
+    const uint32_t testArea = 4;
+    const float cornerRadius = 120.0f;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", bufferWidth, bufferHeight));
+    ASSERT_NO_FATAL_FAILURE(fillLayerColor(layer, Color::BLUE, bufferWidth, bufferHeight));
+
+    sp<SurfaceControl> child;
+    ASSERT_NO_FATAL_FAILURE(child = createLayer("child", bufferWidth, bufferHeight));
+    ASSERT_NO_FATAL_FAILURE(fillLayerColor(child, Color::RED, bufferWidth, bufferHeight));
+
+    Transaction()
+            .reparent(layer, parent)
+            .reparent(child, layer)
+            .setColor(parent, half3(0, 1, 0))
+            .setCrop(parent, Rect(0, 0, layerWidth, layerHeight))
+            .setCornerRadius(parent, cornerRadius) /* */
+
+            .setTransform(layer, ui::Transform::ROT_90)
+            .setDestinationFrame(layer, Rect(0, 0, layerWidth, layerHeight))
+
+            .setTransform(child, ui::Transform::ROT_90)
+            .setDestinationFrame(child, Rect(0, 0, layerWidth, layerHeight))
+            .apply();
+    {
+        auto shot = getScreenCapture();
+        // Corners are transparent
+        // top-left
+        shot->expectColor(Rect(0, 0, testArea, testArea), Color::BLACK);
+        // top-right
+        shot->expectColor(Rect(layerWidth - testArea, 0, layerWidth, testArea), Color::BLACK);
+        // bottom-left
+        shot->expectColor(Rect(0, layerHeight - testArea, testArea, layerHeight), Color::BLACK);
+        // bottom-right
+        shot->expectColor(Rect(layerWidth - testArea, layerHeight - testArea, layerWidth,
+                               layerHeight),
+                          Color::BLACK);
+
+        // Area after corner radius is solid
+        // top-left to top-right under the corner
+        shot->expectColor(Rect(0, cornerRadius, layerWidth, cornerRadius + testArea), Color::RED);
+        // bottom-left to bottom-right above the corner
+        shot->expectColor(Rect(0, layerHeight - cornerRadius - testArea, layerWidth,
+                               layerHeight - cornerRadius),
+                          Color::RED);
+        // left side after the corner
+        shot->expectColor(Rect(cornerRadius, 0, cornerRadius + testArea, layerHeight), Color::RED);
+        // right side before the corner
+        shot->expectColor(Rect(layerWidth - cornerRadius - testArea, 0, layerWidth - cornerRadius,
+                               layerHeight),
+                          Color::RED);
+    }
+}
+
 TEST_P(LayerTypeAndRenderTypeTransactionTest, SetBackgroundBlurRadiusSimple) {
     if (!deviceSupportsBlurs()) GTEST_SKIP();
     if (!deviceUsesSkiaRenderEngine()) GTEST_SKIP();
diff --git a/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp b/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
index 54f4c7c..a9ad249 100644
--- a/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
@@ -66,7 +66,7 @@
             ALOGD("schedule: %zu", token.value());
             if (mCallbacks.count(token) == 0) {
                 ALOGD("schedule: callback %zu not registered", token.value());
-                return scheduler::ScheduleResult::Error;
+                return scheduler::ScheduleResult{};
             }
 
             auto& callback = mCallbacks.at(token);
@@ -75,7 +75,7 @@
             callback.targetWakeupTime =
                     timing.earliestVsync - timing.workDuration - timing.readyDuration;
             ALOGD("schedule: callback %zu scheduled", token.value());
-            return scheduler::ScheduleResult::Scheduled;
+            return scheduler::ScheduleResult{callback.targetWakeupTime};
         });
 
         ON_CALL(*this, cancel).WillByDefault([this](CallbackToken token) {
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index 8208b3f..dbd51fe 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -104,8 +104,12 @@
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = mDuration.count(),
                                                                  .readyDuration = 0,
                                                                  .earliestVsync = 0};
-    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).Times(1);
+    EXPECT_FALSE(mEventQueue.nextExpectedInvalidate().has_value());
+
+    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.invalidate());
+    EXPECT_TRUE(mEventQueue.nextExpectedInvalidate().has_value());
+    EXPECT_EQ(1234, mEventQueue.nextExpectedInvalidate().value().time_since_epoch().count());
 }
 
 TEST_F(MessageQueueTest, invalidateTwice) {
@@ -114,11 +118,15 @@
                                                                  .readyDuration = 0,
                                                                  .earliestVsync = 0};
 
-    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).Times(1);
+    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.invalidate());
+    EXPECT_TRUE(mEventQueue.nextExpectedInvalidate().has_value());
+    EXPECT_EQ(1234, mEventQueue.nextExpectedInvalidate().value().time_since_epoch().count());
 
-    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).Times(1);
+    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.invalidate());
+    EXPECT_TRUE(mEventQueue.nextExpectedInvalidate().has_value());
+    EXPECT_EQ(4567, mEventQueue.nextExpectedInvalidate().value().time_since_epoch().count());
 }
 
 TEST_F(MessageQueueTest, invalidateTwiceWithCallback) {
@@ -127,8 +135,10 @@
                                                                  .readyDuration = 0,
                                                                  .earliestVsync = 0};
 
-    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).Times(1);
+    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.invalidate());
+    EXPECT_TRUE(mEventQueue.nextExpectedInvalidate().has_value());
+    EXPECT_EQ(1234, mEventQueue.nextExpectedInvalidate().value().time_since_epoch().count());
 
     const auto startTime = 100;
     const auto endTime = startTime + mDuration.count();
@@ -141,12 +151,14 @@
     EXPECT_CALL(*mHandler, dispatchInvalidate(vsyncId, presentTime)).Times(1);
     EXPECT_NO_FATAL_FAILURE(mEventQueue.triggerVsyncCallback(presentTime, startTime, endTime));
 
+    EXPECT_FALSE(mEventQueue.nextExpectedInvalidate().has_value());
+
     const auto timingAfterCallback =
             scheduler::VSyncDispatch::ScheduleTiming{.workDuration = mDuration.count(),
                                                      .readyDuration = 0,
                                                      .earliestVsync = presentTime};
 
-    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).Times(1);
+    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.invalidate());
 }
 
@@ -158,7 +170,7 @@
                                                      .readyDuration = 0,
                                                      .earliestVsync = 0};
 
-    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).Times(1);
+    EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0));
     EXPECT_NO_FATAL_FAILURE(mEventQueue.invalidate());
 }
 
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index ff53a7b..3e4e130 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -1011,6 +1011,9 @@
     constexpr size_t MISSED_FRAMES = 4;
     constexpr size_t CLIENT_COMPOSITION_FRAMES = 3;
     constexpr size_t DISPLAY_EVENT_CONNECTIONS = 14;
+    constexpr nsecs_t DISPLAY_DEADLINE_DELTA = 1'000'000;
+    constexpr nsecs_t DISPLAY_PRESENT_JITTER = 2'000'000;
+    constexpr nsecs_t APP_DEADLINE_DELTA = 3'000'000;
 
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
 
@@ -1036,24 +1039,35 @@
     mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(5000000));
 
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::SurfaceFlingerCpuDeadlineMissed, 1, 2, 3});
+                                      JankType::SurfaceFlingerCpuDeadlineMissed,
+                                      DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
+                                      APP_DEADLINE_DELTA});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::SurfaceFlingerGpuDeadlineMissed, 1, 2, 3});
+                                      JankType::SurfaceFlingerGpuDeadlineMissed,
+                                      DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
+                                      APP_DEADLINE_DELTA});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::DisplayHAL, 1, 2, 3});
+                                      JankType::DisplayHAL, DISPLAY_DEADLINE_DELTA,
+                                      DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::AppDeadlineMissed, 1, 2, 3});
+                                      JankType::AppDeadlineMissed, DISPLAY_DEADLINE_DELTA,
+                                      DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::SurfaceFlingerScheduling, 1, 2, 3});
+                                      JankType::SurfaceFlingerScheduling, DISPLAY_DEADLINE_DELTA,
+                                      DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::PredictionError, 1, 2, 3});
+                                      JankType::PredictionError, DISPLAY_DEADLINE_DELTA,
+                                      DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::AppDeadlineMissed | JankType::BufferStuffing, 1, 2,
-                                      3});
+                                      JankType::AppDeadlineMissed | JankType::BufferStuffing,
+                                      DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
+                                      APP_DEADLINE_DELTA});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::BufferStuffing, 1, 2, 3});
+                                      JankType::BufferStuffing, DISPLAY_DEADLINE_DELTA,
+                                      DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::None, 1, 2, 3});
+                                      JankType::None, DISPLAY_DEADLINE_DELTA,
+                                      DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA});
 
     std::string pulledData;
     EXPECT_TRUE(mTimeStats->onPullAtom(10062 /*SURFACEFLINGER_STATS_GLOBAL_INFO*/, &pulledData));
@@ -1137,6 +1151,10 @@
 TEST_F(TimeStatsTest, layerStatsCallback_pullsAllAndClears) {
     constexpr size_t LATE_ACQUIRE_FRAMES = 2;
     constexpr size_t BAD_DESIRED_PRESENT_FRAMES = 3;
+    constexpr nsecs_t DISPLAY_DEADLINE_DELTA = 1'000'000;
+    constexpr nsecs_t DISPLAY_PRESENT_JITTER = 2'000'000;
+    constexpr nsecs_t APP_DEADLINE_DELTA_2MS = 2'000'000;
+    constexpr nsecs_t APP_DEADLINE_DELTA_3MS = 3'000'000;
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
 
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
@@ -1155,22 +1173,32 @@
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000, frameRate60);
 
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::SurfaceFlingerCpuDeadlineMissed, 1, 2, 3});
+                                      JankType::SurfaceFlingerCpuDeadlineMissed,
+                                      DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
+                                      APP_DEADLINE_DELTA_3MS});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::SurfaceFlingerGpuDeadlineMissed, 1, 2, 3});
+                                      JankType::SurfaceFlingerGpuDeadlineMissed,
+                                      DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
+                                      APP_DEADLINE_DELTA_3MS});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::DisplayHAL, 1, 2, 3});
+                                      JankType::DisplayHAL, DISPLAY_DEADLINE_DELTA,
+                                      DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA_3MS});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::AppDeadlineMissed, 1, 2, 3});
+                                      JankType::AppDeadlineMissed, DISPLAY_DEADLINE_DELTA,
+                                      DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA_3MS});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::SurfaceFlingerScheduling, 1, 2, 2});
+                                      JankType::SurfaceFlingerScheduling, DISPLAY_DEADLINE_DELTA,
+                                      DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA_2MS});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::PredictionError, 1, 2, 2});
+                                      JankType::PredictionError, DISPLAY_DEADLINE_DELTA,
+                                      DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA_2MS});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::AppDeadlineMissed | JankType::BufferStuffing, 1, 2,
-                                      2});
+                                      JankType::AppDeadlineMissed | JankType::BufferStuffing,
+                                      DISPLAY_DEADLINE_DELTA, APP_DEADLINE_DELTA_2MS,
+                                      APP_DEADLINE_DELTA_2MS});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      JankType::None, 1, 2, 3});
+                                      JankType::None, DISPLAY_DEADLINE_DELTA,
+                                      DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA_3MS});
 
     std::string pulledData;
     EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index b64cce9..d59d64b 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -232,11 +232,12 @@
         VSyncDispatchTimerQueue mDispatch{createTimeKeeper(), mStubTracker, mDispatchGroupThreshold,
                                           mVsyncMoveThreshold};
         CountingCallback cb(mDispatch);
-        EXPECT_EQ(mDispatch.schedule(cb,
-                                     {.workDuration = 100,
-                                      .readyDuration = 0,
-                                      .earliestVsync = 1000}),
-                  ScheduleResult::Scheduled);
+        const auto result = mDispatch.schedule(cb,
+                                               {.workDuration = 100,
+                                                .readyDuration = 0,
+                                                .earliestVsync = 1000});
+        EXPECT_TRUE(result.has_value());
+        EXPECT_EQ(900, *result);
     }
 }
 
@@ -245,11 +246,13 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900));
 
     CountingCallback cb(mDispatch);
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 100,
-                                  .readyDuration = 0,
-                                  .earliestVsync = intended}),
-              ScheduleResult::Scheduled);
+    const auto result = mDispatch.schedule(cb,
+                                           {.workDuration = 100,
+                                            .readyDuration = 0,
+                                            .earliestVsync = intended});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(900, *result);
+
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
@@ -277,11 +280,12 @@
     EXPECT_CALL(mMockClock, alarmAt(_, mPeriod));
 
     CountingCallback cb(mDispatch);
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = workDuration,
-                                  .readyDuration = 0,
-                                  .earliestVsync = mPeriod}),
-              ScheduleResult::Scheduled);
+    const auto result = mDispatch.schedule(cb,
+                                           {.workDuration = workDuration,
+                                            .readyDuration = 0,
+                                            .earliestVsync = mPeriod});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(mPeriod, *result);
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancel) {
@@ -289,11 +293,11 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     CountingCallback cb(mDispatch);
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 100,
-                                  .readyDuration = 0,
-                                  .earliestVsync = mPeriod}),
-              ScheduleResult::Scheduled);
+    const auto result =
+            mDispatch.schedule(cb,
+                               {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(mPeriod - 100, *result);
     EXPECT_EQ(mDispatch.cancel(cb), CancelResult::Cancelled);
 }
 
@@ -302,11 +306,11 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     CountingCallback cb(mDispatch);
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 100,
-                                  .readyDuration = 0,
-                                  .earliestVsync = mPeriod}),
-              ScheduleResult::Scheduled);
+    const auto result =
+            mDispatch.schedule(cb,
+                               {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(mPeriod - 100, *result);
     mMockClock.advanceBy(950);
     EXPECT_EQ(mDispatch.cancel(cb), CancelResult::TooLate);
 }
@@ -316,11 +320,11 @@
     EXPECT_CALL(mMockClock, alarmCancel());
 
     PausingCallback cb(mDispatch, std::chrono::duration_cast<std::chrono::milliseconds>(1s));
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 100,
-                                  .readyDuration = 0,
-                                  .earliestVsync = mPeriod}),
-              ScheduleResult::Scheduled);
+    const auto result =
+            mDispatch.schedule(cb,
+                               {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(mPeriod - 100, *result);
 
     std::thread pausingThread([&] { mMockClock.advanceToNextCallback(); });
     EXPECT_TRUE(cb.waitForPause());
@@ -337,11 +341,11 @@
 
     PausingCallback cb(mDispatch, 50ms);
     cb.stashResource(resource);
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 100,
-                                  .readyDuration = 0,
-                                  .earliestVsync = mPeriod}),
-              ScheduleResult::Scheduled);
+    const auto result =
+            mDispatch.schedule(cb,
+                               {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(mPeriod - 100, *result);
 
     std::thread pausingThread([&] { mMockClock.advanceToNextCallback(); });
     EXPECT_TRUE(cb.waitForPause());
@@ -535,21 +539,25 @@
     std::optional<nsecs_t> lastTarget;
     tmp = mDispatch.registerCallback(
             [&](auto timestamp, auto, auto) {
-                EXPECT_EQ(mDispatch.schedule(tmp,
-                                             {.workDuration = 400,
-                                              .readyDuration = 0,
-                                              .earliestVsync = timestamp - mVsyncMoveThreshold}),
-                          ScheduleResult::Scheduled);
-                EXPECT_EQ(mDispatch.schedule(tmp,
-                                             {.workDuration = 400,
-                                              .readyDuration = 0,
-                                              .earliestVsync = timestamp}),
-                          ScheduleResult::Scheduled);
-                EXPECT_EQ(mDispatch.schedule(tmp,
-                                             {.workDuration = 400,
-                                              .readyDuration = 0,
-                                              .earliestVsync = timestamp + mVsyncMoveThreshold}),
-                          ScheduleResult::Scheduled);
+                auto result =
+                        mDispatch.schedule(tmp,
+                                           {.workDuration = 400,
+                                            .readyDuration = 0,
+                                            .earliestVsync = timestamp - mVsyncMoveThreshold});
+                EXPECT_TRUE(result.has_value());
+                EXPECT_EQ(mPeriod + timestamp - 400, *result);
+                result = mDispatch.schedule(tmp,
+                                            {.workDuration = 400,
+                                             .readyDuration = 0,
+                                             .earliestVsync = timestamp});
+                EXPECT_TRUE(result.has_value());
+                EXPECT_EQ(mPeriod + timestamp - 400, *result);
+                result = mDispatch.schedule(tmp,
+                                            {.workDuration = 400,
+                                             .readyDuration = 0,
+                                             .earliestVsync = timestamp + mVsyncMoveThreshold});
+                EXPECT_TRUE(result.has_value());
+                EXPECT_EQ(mPeriod + timestamp - 400, *result);
                 lastTarget = timestamp;
             },
             "oo");
@@ -627,36 +635,41 @@
 
 TEST_F(VSyncDispatchTimerQueueTest, makingUpIdsError) {
     VSyncDispatch::CallbackToken token(100);
-    EXPECT_THAT(mDispatch.schedule(token,
-                                   {.workDuration = 100,
-                                    .readyDuration = 0,
-                                    .earliestVsync = 1000}),
-                Eq(ScheduleResult::Error));
+    EXPECT_FALSE(mDispatch
+                         .schedule(token,
+                                   {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000})
+                         .has_value());
     EXPECT_THAT(mDispatch.cancel(token), Eq(CancelResult::Error));
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, canMoveCallbackBackwardsInTime) {
     CountingCallback cb0(mDispatch);
-    EXPECT_EQ(mDispatch.schedule(cb0,
-                                 {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
-    EXPECT_EQ(mDispatch.schedule(cb0,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
+    auto result =
+            mDispatch.schedule(cb0,
+                               {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(500, *result);
+    result = mDispatch.schedule(cb0,
+                                {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(900, *result);
 }
 
 // b/1450138150
 TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipAScheduledTargetVSync) {
     EXPECT_CALL(mMockClock, alarmAt(_, 500));
     CountingCallback cb(mDispatch);
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
+    auto result =
+            mDispatch.schedule(cb,
+                               {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(500, *result);
     mMockClock.advanceBy(400);
 
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
+    result = mDispatch.schedule(cb,
+                                {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(1200, *result);
     advanceToNextCallback();
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
 }
@@ -667,24 +680,30 @@
             .WillOnce(Return(1000))
             .WillOnce(Return(1002));
     CountingCallback cb(mDispatch);
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
+    auto result =
+            mDispatch.schedule(cb,
+                               {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(500, *result);
     mMockClock.advanceBy(400);
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
+    result = mDispatch.schedule(cb,
+                                {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(602, *result);
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, canScheduleNegativeOffsetAgainstDifferentPeriods) {
     CountingCallback cb0(mDispatch);
-    EXPECT_EQ(mDispatch.schedule(cb0,
-                                 {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
+    auto result =
+            mDispatch.schedule(cb0,
+                               {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(500, *result);
     advanceToNextCallback();
-    EXPECT_EQ(mDispatch.schedule(cb0,
-                                 {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000}),
-              ScheduleResult::Scheduled);
+    result = mDispatch.schedule(cb0,
+                                {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(900, *result);
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, canScheduleLargeNegativeOffset) {
@@ -692,26 +711,32 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
     EXPECT_CALL(mMockClock, alarmAt(_, 1100)).InSequence(seq);
     CountingCallback cb0(mDispatch);
-    EXPECT_EQ(mDispatch.schedule(cb0,
-                                 {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
+    auto result =
+            mDispatch.schedule(cb0,
+                               {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(500, *result);
     advanceToNextCallback();
-    EXPECT_EQ(mDispatch.schedule(cb0,
-                                 {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000}),
-              ScheduleResult::Scheduled);
+    result = mDispatch.schedule(cb0,
+                                {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(1100, *result);
 }
 
 TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesNotAffectSchedulingState) {
     EXPECT_CALL(mMockClock, alarmAt(_, 600));
 
     CountingCallback cb(mDispatch);
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
+    auto result =
+            mDispatch.schedule(cb,
+                               {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(600, *result);
 
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
+    result = mDispatch.schedule(cb,
+                                {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(600, *result);
 
     advanceToNextCallback();
 }
@@ -754,16 +779,19 @@
     CountingCallback cb1(mDispatch);
     CountingCallback cb2(mDispatch);
 
-    EXPECT_EQ(mDispatch.schedule(cb1,
-                                 {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
+    auto result =
+            mDispatch.schedule(cb1,
+                               {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(600, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
 
-    EXPECT_EQ(mDispatch.schedule(cb2,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}),
-              ScheduleResult::Scheduled);
+    result = mDispatch.schedule(cb2,
+                                {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(1900, *result);
     mMockClock.advanceBy(80);
 
     EXPECT_THAT(cb1.mCalls.size(), Eq(1));
@@ -779,16 +807,19 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 1630)).InSequence(seq);
     CountingCallback cb(mDispatch);
 
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
+    auto result =
+            mDispatch.schedule(cb,
+                               {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(600, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
 
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000}),
-              ScheduleResult::Scheduled);
+    result = mDispatch.schedule(cb,
+                                {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(1630, *result);
     mMockClock.advanceBy(80);
 
     EXPECT_THAT(cb.mCalls.size(), Eq(1));
@@ -802,12 +833,15 @@
     CountingCallback cb1(mDispatch);
     CountingCallback cb2(mDispatch);
 
-    EXPECT_EQ(mDispatch.schedule(cb1,
-                                 {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
-    EXPECT_EQ(mDispatch.schedule(cb2,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}),
-              ScheduleResult::Scheduled);
+    auto result =
+            mDispatch.schedule(cb1,
+                               {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(600, *result);
+    result = mDispatch.schedule(cb2,
+                                {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(1900, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
@@ -828,12 +862,15 @@
     CountingCallback cb1(mDispatch);
     CountingCallback cb2(mDispatch);
 
-    EXPECT_EQ(mDispatch.schedule(cb1,
-                                 {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
-    EXPECT_EQ(mDispatch.schedule(cb2,
-                                 {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}),
-              ScheduleResult::Scheduled);
+    auto result =
+            mDispatch.schedule(cb1,
+                               {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(600, *result);
+    result = mDispatch.schedule(cb2,
+                                {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(1900, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(620);
@@ -861,12 +898,15 @@
             .InSequence(seq)
             .WillOnce(Return(1000));
 
-    EXPECT_EQ(mDispatch.schedule(cb1,
-                                 {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
-    EXPECT_EQ(mDispatch.schedule(cb2,
-                                 {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000}),
-              ScheduleResult::Scheduled);
+    auto result =
+            mDispatch.schedule(cb1,
+                               {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(600, *result);
+    result = mDispatch.schedule(cb2,
+                                {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(610, *result);
 
     mMockClock.setLag(100);
     mMockClock.advanceBy(700);
@@ -886,11 +926,12 @@
     EXPECT_CALL(mMockClock, alarmAt(_, 900));
 
     CountingCallback cb(mDispatch);
-    EXPECT_EQ(mDispatch.schedule(cb,
-                                 {.workDuration = 70,
-                                  .readyDuration = 30,
-                                  .earliestVsync = intended}),
-              ScheduleResult::Scheduled);
+    const auto result = mDispatch.schedule(cb,
+                                           {.workDuration = 70,
+                                            .readyDuration = 30,
+                                            .earliestVsync = intended});
+    EXPECT_TRUE(result.has_value());
+    EXPECT_EQ(900, *result);
     advanceToNextCallback();
 
     ASSERT_THAT(cb.mCalls.size(), Eq(1));
@@ -922,9 +963,9 @@
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     EXPECT_FALSE(entry.wakeupTime());
-    EXPECT_THAT(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(*wakeup, Eq(900));
@@ -944,9 +985,9 @@
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
 
     EXPECT_FALSE(entry.wakeupTime());
-    EXPECT_THAT(entry.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 994},
-                               mStubTracker, now),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 994},
+                               mStubTracker, now)
+                        .has_value());
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(*wakeup, Eq(9500));
@@ -967,9 +1008,9 @@
             },
             mVsyncMoveThreshold);
 
-    EXPECT_THAT(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(*wakeup, Eq(900));
@@ -1002,9 +1043,9 @@
     entry.update(mStubTracker, 0);
     EXPECT_FALSE(entry.wakeupTime());
 
-    EXPECT_THAT(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
     auto wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(wakeup, Eq(900));
@@ -1018,9 +1059,9 @@
 TEST_F(VSyncDispatchTimerQueueEntryTest, skipsUpdateIfJustScheduled) {
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
-    EXPECT_THAT(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
     entry.update(mStubTracker, 0);
 
     auto const wakeup = entry.wakeupTime();
@@ -1031,26 +1072,26 @@
 TEST_F(VSyncDispatchTimerQueueEntryTest, willSnapToNextTargettableVSync) {
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
-    EXPECT_THAT(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
     entry.executing(); // 1000 is executing
     // had 1000 not been executing, this could have been scheduled for time 800.
-    EXPECT_THAT(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
 
-    EXPECT_THAT(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1950));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
 
-    EXPECT_THAT(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 1001},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 1001},
+                               mStubTracker, 0)
+                        .has_value());
     EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
     EXPECT_THAT(*entry.readyTime(), Eq(2000));
 }
@@ -1071,32 +1112,32 @@
             .InSequence(seq)
             .WillOnce(Return(2000));
 
-    EXPECT_THAT(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
 
     entry.executing(); // 1000 is executing
 
-    EXPECT_THAT(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
 }
 
 TEST_F(VSyncDispatchTimerQueueEntryTest, reportsScheduledIfStillTime) {
     VSyncDispatchTimerQueueEntry entry(
             "test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
-    EXPECT_THAT(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
-    EXPECT_THAT(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
-    EXPECT_THAT(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
-    EXPECT_THAT(entry.schedule({.workDuration = 1200, .readyDuration = 0, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
+    EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
+    EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
+    EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
 }
 
 TEST_F(VSyncDispatchTimerQueueEntryTest, storesPendingUpdatesUntilUpdate) {
@@ -1128,9 +1169,9 @@
             },
             mVsyncMoveThreshold);
 
-    EXPECT_THAT(entry.schedule({.workDuration = 70, .readyDuration = 30, .earliestVsync = 500},
-                               mStubTracker, 0),
-                Eq(ScheduleResult::Scheduled));
+    EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .earliestVsync = 500},
+                               mStubTracker, 0)
+                        .has_value());
     auto const wakeup = entry.wakeupTime();
     ASSERT_TRUE(wakeup);
     EXPECT_THAT(*wakeup, Eq(900));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.h b/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.h
index 453c93a..0e7b320 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.h
@@ -39,6 +39,7 @@
                  void(scheduler::VSyncDispatch&, frametimeline::TokenManager&,
                       std::chrono::nanoseconds));
     MOCK_METHOD1(setDuration, void(std::chrono::nanoseconds workDuration));
+    MOCK_METHOD0(nextExpectedInvalidate, std::optional<std::chrono::steady_clock::time_point>());
 };
 
 } // namespace android::mock