Merge "Requantize by resolution / 8" into rvc-dev
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index d4c1616..942c16c 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -1367,13 +1367,11 @@
     printf("\n");
 }
 
-static void DumpstateArcOnly() {
+static void DumpstateLimitedOnly() {
     // Trimmed-down version of dumpstate to only include a whitelisted
     // set of logs (system log, event log, and system server / system app
-    // crashes, and ARC networking logs). See b/136273873 and b/138459828
-    // for context. New sections must be first approved by Chrome OS Privacy
-    // and then added to server side cros monitoring PII scrubber before adding
-    // them here. See cl/312126645 for an example.
+    // crashes, and networking logs). See b/136273873 and b/138459828
+    // for context.
     DurationReporter duration_reporter("DUMPSTATE");
     unsigned long timeout_ms;
     // calculate timeout
@@ -1391,11 +1389,6 @@
     printf("== Networking Service\n");
     printf("========================================================\n");
 
-    // ARC networking service implements dumpsys by reusing the 'wifi' service name.
-    // The top-level handler is implemented in handleDump() in
-    // vendor/google_arc/libs/arc-services/src/com/android/server/arc/net/ArcNetworkService.java.
-    // It outputs a subset of Android system server state relevant for debugging ARC
-    // connectivity issues, in a PII-free manner. See b/147270970.
     RunDumpsys("DUMPSYS NETWORK_SERVICE_LIMITED", {"wifi", "-a"},
                CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10));
 
@@ -2100,7 +2093,7 @@
 static void ShowUsage() {
     fprintf(stderr,
             "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-d] [-p] "
-            "[-z] [-s] [-S] [-q] [-P] [-R] [-A] [-V version]\n"
+            "[-z] [-s] [-S] [-q] [-P] [-R] [-L] [-V version]\n"
             "  -h: display this help message\n"
             "  -b: play sound file instead of vibrate, at beginning of job\n"
             "  -e: play sound file instead of vibrate, at end of job\n"
@@ -2113,7 +2106,7 @@
             "  -P: send broadcast when started and do progress updates\n"
             "  -R: take bugreport in remote mode (requires -z and -d, shouldn't be used with -P)\n"
             "  -w: start binder service and make it wait for a call to startBugreport\n"
-            "  -A: output limited information that is safe for submission in ARC++ bugreports\n"
+            "  -L: output limited information that is safe for submission in feedback reports\n"
             "  -v: prints the dumpstate header and exit\n");
 }
 
@@ -2359,12 +2352,12 @@
         "do_zip_file: %d do_vibrate: %d use_socket: %d use_control_socket: %d do_screenshot: %d "
         "is_remote_mode: %d show_header_only: %d do_start_service: %d telephony_only: %d "
         "wifi_only: %d do_progress_updates: %d fd: %d bugreport_mode: %s dumpstate_hal_mode: %s "
-        "arc_only: %d args: %s\n",
+        "limited_only: %d args: %s\n",
         options.do_zip_file, options.do_vibrate, options.use_socket, options.use_control_socket,
         options.do_screenshot, options.is_remote_mode, options.show_header_only,
         options.do_start_service, options.telephony_only, options.wifi_only,
         options.do_progress_updates, options.bugreport_fd.get(), options.bugreport_mode.c_str(),
-        toString(options.dumpstate_hal_mode).c_str(), options.arc_only, options.args.c_str());
+        toString(options.dumpstate_hal_mode).c_str(), options.limited_only, options.args.c_str());
 }
 
 void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode,
@@ -2386,7 +2379,7 @@
 Dumpstate::RunStatus Dumpstate::DumpOptions::Initialize(int argc, char* argv[]) {
     RunStatus status = RunStatus::OK;
     int c;
-    while ((c = getopt(argc, argv, "dho:svqzpAPBRSV:w")) != -1) {
+    while ((c = getopt(argc, argv, "dho:svqzpLPBRSV:w")) != -1) {
         switch (c) {
             // clang-format off
             case 'd': do_add_date = true;            break;
@@ -2398,7 +2391,7 @@
             case 'p': do_screenshot = true;          break;
             case 'P': do_progress_updates = true;    break;
             case 'R': is_remote_mode = true;         break;
-            case 'A': arc_only = true;               break;
+            case 'L': limited_only = true;           break;
             case 'V':                                break;  // compatibility no-op
             case 'w':
                 // This was already processed
@@ -2683,7 +2676,7 @@
     // duration is logged into MYLOG instead.
     PrintHeader();
 
-    // TODO(nandana) reduce code repetition in if branches
+    // TODO(b/158737089) reduce code repetition in if branches
     if (options_->telephony_only) {
         MaybeTakeEarlyScreenshot();
         onUiIntensiveBugreportDumpsFinished(calling_uid, calling_package);
@@ -2695,11 +2688,11 @@
         onUiIntensiveBugreportDumpsFinished(calling_uid, calling_package);
         MaybeCheckUserConsent(calling_uid, calling_package);
         DumpstateWifiOnly();
-    } else if (options_->arc_only) {
+    } else if (options_->limited_only) {
         MaybeTakeEarlyScreenshot();
         onUiIntensiveBugreportDumpsFinished(calling_uid, calling_package);
         MaybeCheckUserConsent(calling_uid, calling_package);
-        DumpstateArcOnly();
+        DumpstateLimitedOnly();
     } else {
         // Invoke critical dumpsys first to preserve system state, before doing anything else.
         RunDumpsysCritical();
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index c8c9f45..dc0848a 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -378,7 +378,7 @@
         bool telephony_only = false;
         bool wifi_only = false;
         // Trimmed-down version of dumpstate to only include whitelisted logs.
-        bool arc_only = false;
+        bool limited_only = false;
         // Whether progress updates should be published.
         bool do_progress_updates = false;
         // The mode we'll use when calling IDumpstateDevice::dumpstateBoard.
diff --git a/cmds/dumpstate/main.cpp b/cmds/dumpstate/main.cpp
index f1342a5..ec89c0d 100644
--- a/cmds/dumpstate/main.cpp
+++ b/cmds/dumpstate/main.cpp
@@ -30,7 +30,7 @@
     bool do_wait = false;
     int c;
     // Keep flags in sync with Dumpstate::DumpOptions::Initialize.
-    while ((c = getopt(argc, argv, "dho:svqzpAPBRSV:w")) != -1 && !do_wait) {
+    while ((c = getopt(argc, argv, "dho:svqzpLPBRSV:w")) != -1 && !do_wait) {
         switch (c) {
             case 'w':
                 do_wait = true;
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index 7078521..e94e51c 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -179,7 +179,7 @@
     EXPECT_FALSE(options_.do_screenshot);
     EXPECT_FALSE(options_.do_progress_updates);
     EXPECT_FALSE(options_.is_remote_mode);
-    EXPECT_FALSE(options_.arc_only);
+    EXPECT_FALSE(options_.limited_only);
     EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
 }
 
@@ -207,7 +207,7 @@
     EXPECT_FALSE(options_.do_progress_updates);
     EXPECT_FALSE(options_.is_remote_mode);
     EXPECT_FALSE(options_.use_socket);
-    EXPECT_FALSE(options_.arc_only);
+    EXPECT_FALSE(options_.limited_only);
     EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
 }
 
@@ -233,7 +233,7 @@
     EXPECT_FALSE(options_.do_screenshot);
     EXPECT_FALSE(options_.do_progress_updates);
     EXPECT_FALSE(options_.is_remote_mode);
-    EXPECT_FALSE(options_.arc_only);
+    EXPECT_FALSE(options_.limited_only);
     EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
 }
 
@@ -252,7 +252,7 @@
     EXPECT_FALSE(options_.is_remote_mode);
     EXPECT_FALSE(options_.use_socket);
     EXPECT_FALSE(options_.do_start_service);
-    EXPECT_FALSE(options_.arc_only);
+    EXPECT_FALSE(options_.limited_only);
 }
 
 TEST_F(DumpOptionsTest, InitializeInteractiveBugReport) {
@@ -270,7 +270,7 @@
     EXPECT_FALSE(options_.show_header_only);
     EXPECT_FALSE(options_.is_remote_mode);
     EXPECT_FALSE(options_.use_socket);
-    EXPECT_FALSE(options_.arc_only);
+    EXPECT_FALSE(options_.limited_only);
 }
 
 TEST_F(DumpOptionsTest, InitializeRemoteBugReport) {
@@ -287,7 +287,7 @@
     EXPECT_FALSE(options_.show_header_only);
     EXPECT_FALSE(options_.do_progress_updates);
     EXPECT_FALSE(options_.use_socket);
-    EXPECT_FALSE(options_.arc_only);
+    EXPECT_FALSE(options_.limited_only);
 }
 
 TEST_F(DumpOptionsTest, InitializeWearBugReport) {
@@ -305,7 +305,7 @@
     EXPECT_FALSE(options_.show_header_only);
     EXPECT_FALSE(options_.is_remote_mode);
     EXPECT_FALSE(options_.use_socket);
-    EXPECT_FALSE(options_.arc_only);
+    EXPECT_FALSE(options_.limited_only);
 }
 
 TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) {
@@ -323,7 +323,7 @@
     EXPECT_FALSE(options_.show_header_only);
     EXPECT_FALSE(options_.is_remote_mode);
     EXPECT_FALSE(options_.use_socket);
-    EXPECT_FALSE(options_.arc_only);
+    EXPECT_FALSE(options_.limited_only);
 }
 
 TEST_F(DumpOptionsTest, InitializeWifiBugReport) {
@@ -341,10 +341,10 @@
     EXPECT_FALSE(options_.do_progress_updates);
     EXPECT_FALSE(options_.is_remote_mode);
     EXPECT_FALSE(options_.use_socket);
-    EXPECT_FALSE(options_.arc_only);
+    EXPECT_FALSE(options_.limited_only);
 }
 
-TEST_F(DumpOptionsTest, InitializeArcOnlyBugreport) {
+TEST_F(DumpOptionsTest, InitializeLimitedOnlyBugreport) {
     // clang-format off
     char* argv[] = {
         const_cast<char*>("dumpstatez"),
@@ -352,7 +352,7 @@
         const_cast<char*>("-d"),
         const_cast<char*>("-z"),
         const_cast<char*>("-q"),
-        const_cast<char*>("-A")
+        const_cast<char*>("-L")
     };
     // clang-format on
 
@@ -363,7 +363,7 @@
     EXPECT_TRUE(options_.do_zip_file);
     EXPECT_TRUE(options_.use_control_socket);
     EXPECT_FALSE(options_.do_vibrate);
-    EXPECT_TRUE(options_.arc_only);
+    EXPECT_TRUE(options_.limited_only);
 
     // Other options retain default values
     EXPECT_FALSE(options_.show_header_only);
@@ -400,7 +400,7 @@
     EXPECT_FALSE(options_.is_remote_mode);
     EXPECT_FALSE(options_.use_socket);
     EXPECT_FALSE(options_.wifi_only);
-    EXPECT_FALSE(options_.arc_only);
+    EXPECT_FALSE(options_.limited_only);
 }
 
 TEST_F(DumpOptionsTest, InitializePartial1) {
@@ -430,7 +430,7 @@
     EXPECT_FALSE(options_.do_screenshot);
     EXPECT_FALSE(options_.do_progress_updates);
     EXPECT_FALSE(options_.is_remote_mode);
-    EXPECT_FALSE(options_.arc_only);
+    EXPECT_FALSE(options_.limited_only);
     EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
 }
 
@@ -460,7 +460,7 @@
     EXPECT_FALSE(options_.do_zip_file);
     EXPECT_FALSE(options_.use_socket);
     EXPECT_FALSE(options_.use_control_socket);
-    EXPECT_FALSE(options_.arc_only);
+    EXPECT_FALSE(options_.limited_only);
     EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
 }
 
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index acd76b0..228b8a0 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -136,7 +136,7 @@
 
 class LayerHistoryV2 : public android::scheduler::LayerHistory {
 public:
-    LayerHistoryV2();
+    LayerHistoryV2(const scheduler::RefreshRateConfigs&);
     virtual ~LayerHistoryV2();
 
     // Layers are unregistered when the weak reference expires.
diff --git a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
index 316000c..ee612b0 100644
--- a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
@@ -58,37 +58,32 @@
     return atoi(value);
 }
 
-void trace(const wp<Layer>& weak, LayerHistory::LayerVoteType type, int fps) {
+void trace(const wp<Layer>& weak, const LayerInfoV2& info, LayerHistory::LayerVoteType type,
+           int fps) {
     const auto layer = weak.promote();
     if (!layer) return;
 
-    const auto makeTag = [layer](LayerHistory::LayerVoteType vote) {
-        return "LFPS " + RefreshRateConfigs::layerVoteTypeString(vote) + " " + layer->getName();
+    const auto traceType = [&](LayerHistory::LayerVoteType checkedType, int value) {
+        ATRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
     };
 
-    const auto noVoteTag = makeTag(LayerHistory::LayerVoteType::NoVote);
-    const auto heuristicVoteTag = makeTag(LayerHistory::LayerVoteType::Heuristic);
-    const auto explicitDefaultVoteTag = makeTag(LayerHistory::LayerVoteType::ExplicitDefault);
-    const auto explicitExactOrMultipleVoteTag =
-            makeTag(LayerHistory::LayerVoteType::ExplicitExactOrMultiple);
-    const auto minVoteTag = makeTag(LayerHistory::LayerVoteType::Min);
-    const auto maxVoteTag = makeTag(LayerHistory::LayerVoteType::Max);
-
-    ATRACE_INT(noVoteTag.c_str(), type == LayerHistory::LayerVoteType::NoVote ? 1 : 0);
-    ATRACE_INT(heuristicVoteTag.c_str(), type == LayerHistory::LayerVoteType::Heuristic ? fps : 0);
-    ATRACE_INT(explicitDefaultVoteTag.c_str(),
-               type == LayerHistory::LayerVoteType::ExplicitDefault ? fps : 0);
-    ATRACE_INT(explicitExactOrMultipleVoteTag.c_str(),
-               type == LayerHistory::LayerVoteType::ExplicitExactOrMultiple ? fps : 0);
-    ATRACE_INT(minVoteTag.c_str(), type == LayerHistory::LayerVoteType::Min ? 1 : 0);
-    ATRACE_INT(maxVoteTag.c_str(), type == LayerHistory::LayerVoteType::Max ? 1 : 0);
+    traceType(LayerHistory::LayerVoteType::NoVote, 1);
+    traceType(LayerHistory::LayerVoteType::Heuristic, fps);
+    traceType(LayerHistory::LayerVoteType::ExplicitDefault, fps);
+    traceType(LayerHistory::LayerVoteType::ExplicitExactOrMultiple, fps);
+    traceType(LayerHistory::LayerVoteType::Min, 1);
+    traceType(LayerHistory::LayerVoteType::Max, 1);
 
     ALOGD("%s: %s @ %d Hz", __FUNCTION__, layer->getName().c_str(), fps);
 }
 } // namespace
 
-LayerHistoryV2::LayerHistoryV2()
-      : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {}
+LayerHistoryV2::LayerHistoryV2(const scheduler::RefreshRateConfigs& refreshRateConfigs)
+      : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {
+    LayerInfoV2::setTraceEnabled(mTraceEnabled);
+    LayerInfoV2::setRefreshRateConfigs(refreshRateConfigs);
+}
+
 LayerHistoryV2::~LayerHistoryV2() = default;
 
 void LayerHistoryV2::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
@@ -151,7 +146,7 @@
         summary.push_back({strong->getName(), type, refreshRate, weight});
 
         if (CC_UNLIKELY(mTraceEnabled)) {
-            trace(layer, type, static_cast<int>(std::round(refreshRate)));
+            trace(layer, *info, type, static_cast<int>(std::round(refreshRate)));
         }
     }
 
@@ -190,7 +185,7 @@
         }
 
         if (CC_UNLIKELY(mTraceEnabled)) {
-            trace(weak, LayerHistory::LayerVoteType::NoVote, 0);
+            trace(weak, *info, LayerHistory::LayerVoteType::NoVote, 0);
         }
 
         info->onLayerInactive(now);
@@ -217,4 +212,5 @@
         info->clearHistory(systemTime());
     }
 }
+
 } // namespace android::scheduler::impl
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index cb81ca2..820624b 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -98,9 +98,9 @@
                 return false;
             }
 
-            // The layer had to publish at least HISTORY_SIZE or HISTORY_TIME of updates
+            // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
             if (mElements.size() < HISTORY_SIZE &&
-                mElements.back() - mElements.front() < HISTORY_TIME.count()) {
+                mElements.back() - mElements.front() < HISTORY_DURATION.count()) {
                 return false;
             }
 
@@ -124,7 +124,7 @@
 
     private:
         std::deque<nsecs_t> mElements;
-        static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
+        static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
     };
 
     friend class LayerHistoryTest;
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
index 82da007..44f20d0 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
@@ -15,24 +15,31 @@
  */
 
 // #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include "LayerInfoV2.h"
 
 #include <algorithm>
 #include <utility>
 
+#include <cutils/compiler.h>
+#include <cutils/trace.h>
+
 #undef LOG_TAG
 #define LOG_TAG "LayerInfoV2"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 namespace android::scheduler {
 
+const RefreshRateConfigs* LayerInfoV2::sRefreshRateConfigs = nullptr;
+bool LayerInfoV2::sTraceEnabled = false;
+
 LayerInfoV2::LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
                          LayerHistory::LayerVoteType defaultVote)
       : mName(name),
         mHighRefreshRatePeriod(highRefreshRatePeriod),
         mDefaultVote(defaultVote),
-        mLayerVote({defaultVote, 0.0f}) {}
+        mLayerVote({defaultVote, 0.0f}),
+        mRefreshRateHistory(name) {}
 
 void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now,
                                      LayerUpdateType updateType, bool pendingConfigChange) {
@@ -92,24 +99,28 @@
 }
 
 bool LayerInfoV2::hasEnoughDataForHeuristic() const {
-    // The layer had to publish at least HISTORY_SIZE or HISTORY_TIME of updates
+    // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
     if (mFrameTimes.size() < 2) {
+        ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
         return false;
     }
 
     if (!isFrameTimeValid(mFrameTimes.front())) {
+        ALOGV("stale frames still captured");
         return false;
     }
 
-    if (mFrameTimes.size() < HISTORY_SIZE &&
-        mFrameTimes.back().queueTime - mFrameTimes.front().queueTime < HISTORY_TIME.count()) {
+    const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
+    if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
+        ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
+              totalDuration / 1e9f);
         return false;
     }
 
     return true;
 }
 
-std::pair<nsecs_t, bool> LayerInfoV2::calculateAverageFrameTime() const {
+std::optional<nsecs_t> LayerInfoV2::calculateAverageFrameTime() const {
     nsecs_t totalPresentTimeDeltas = 0;
     nsecs_t totalQueueTimeDeltas = 0;
     bool missingPresentTime = false;
@@ -117,15 +128,20 @@
     for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
         // Ignore frames captured during a config change
         if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
-            continue;
+            return std::nullopt;
         }
 
         totalQueueTimeDeltas +=
                 std::max(((it + 1)->queueTime - it->queueTime), mHighRefreshRatePeriod);
         numFrames++;
 
-        if (it->presetTime == 0 || (it + 1)->presetTime == 0) {
+        if (!missingPresentTime && (it->presetTime == 0 || (it + 1)->presetTime == 0)) {
             missingPresentTime = true;
+            // If there are no presentation timestamps and we haven't calculated
+            // one in the past then we can't calculate the refresh rate
+            if (mLastRefreshRate.reported == 0) {
+                return std::nullopt;
+            }
             continue;
         }
 
@@ -140,58 +156,46 @@
     // when implementing render ahead for specific refresh rates. When hwui no longer provides
     // presentation timestamps we look at the queue time to see if the current refresh rate still
     // matches the content.
+
     const auto averageFrameTime =
             static_cast<float>(missingPresentTime ? totalQueueTimeDeltas : totalPresentTimeDeltas) /
             numFrames;
-    return {static_cast<nsecs_t>(averageFrameTime), missingPresentTime};
+    return static_cast<nsecs_t>(averageFrameTime);
 }
 
-bool LayerInfoV2::isRefreshRateStable(nsecs_t averageFrameTime, bool missingPresentTime) const {
-    for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
-        // Ignore frames captured during a config change
-        if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
-            continue;
-        }
-        const auto presentTimeDeltas = [&] {
-            const auto delta = missingPresentTime ? (it + 1)->queueTime - it->queueTime
-                                                  : (it + 1)->presetTime - it->presetTime;
-            return std::max(delta, mHighRefreshRatePeriod);
-        }();
-
-        if (std::abs(presentTimeDeltas - averageFrameTime) > 2 * averageFrameTime) {
-            return false;
-        }
-    }
-
-    return true;
-}
-
-std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible() {
+std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible(nsecs_t now) {
     static constexpr float MARGIN = 1.0f; // 1Hz
-
     if (!hasEnoughDataForHeuristic()) {
         ALOGV("Not enough data");
         return std::nullopt;
     }
 
-    const auto [averageFrameTime, missingPresentTime] = calculateAverageFrameTime();
+    const auto averageFrameTime = calculateAverageFrameTime();
+    if (averageFrameTime.has_value()) {
+        const auto refreshRate = 1e9f / *averageFrameTime;
+        const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
+        if (refreshRateConsistent) {
+            const auto knownRefreshRate =
+                    sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
 
-    // If there are no presentation timestamps provided we can't calculate the refresh rate
-    if (missingPresentTime && mLastReportedRefreshRate == 0) {
-        return std::nullopt;
+            // To avoid oscillation, use the last calculated refresh rate if it is
+            // close enough
+            if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
+                mLastRefreshRate.reported != knownRefreshRate) {
+                mLastRefreshRate.calculated = refreshRate;
+                mLastRefreshRate.reported = knownRefreshRate;
+            }
+
+            ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),
+                  refreshRate, mLastRefreshRate.reported);
+        } else {
+            ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),
+                  refreshRate, mLastRefreshRate.reported);
+        }
     }
 
-    if (!isRefreshRateStable(averageFrameTime, missingPresentTime)) {
-        return std::nullopt;
-    }
-
-    const auto refreshRate = 1e9f / averageFrameTime;
-    if (std::abs(refreshRate - mLastReportedRefreshRate) > MARGIN) {
-        mLastReportedRefreshRate = refreshRate;
-    }
-
-    ALOGV("Refresh rate: %.2f", mLastReportedRefreshRate);
-    return mLastReportedRefreshRate;
+    return mLastRefreshRate.reported == 0 ? std::nullopt
+                                          : std::make_optional(mLastRefreshRate.reported);
 }
 
 std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {
@@ -202,15 +206,24 @@
 
     if (isAnimating(now)) {
         ALOGV("%s is animating", mName.c_str());
+        mLastRefreshRate.animatingOrInfrequent = true;
         return {LayerHistory::LayerVoteType::Max, 0};
     }
 
     if (!isFrequent(now)) {
         ALOGV("%s is infrequent", mName.c_str());
+        mLastRefreshRate.animatingOrInfrequent = true;
         return {LayerHistory::LayerVoteType::Min, 0};
     }
 
-    auto refreshRate = calculateRefreshRateIfPossible();
+    // If the layer was previously tagged as animating or infrequent, we clear
+    // the history as it is likely the layer just changed its behavior
+    // and we should not look at stale data
+    if (mLastRefreshRate.animatingOrInfrequent) {
+        clearHistory(now);
+    }
+
+    auto refreshRate = calculateRefreshRateIfPossible(now);
     if (refreshRate.has_value()) {
         ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
         return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
@@ -220,4 +233,65 @@
     return {LayerHistory::LayerVoteType::Max, 0};
 }
 
+const char* LayerInfoV2::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
+    if (mTraceTags.count(type) == 0) {
+        const auto tag = "LFPS " + mName + " " + RefreshRateConfigs::layerVoteTypeString(type);
+        mTraceTags.emplace(type, tag);
+    }
+
+    return mTraceTags.at(type).c_str();
+}
+
+LayerInfoV2::RefreshRateHistory::HeuristicTraceTagData
+LayerInfoV2::RefreshRateHistory::makeHeuristicTraceTagData() const {
+    const std::string prefix = "LFPS ";
+    const std::string suffix = "Heuristic ";
+    return {.min = prefix + mName + suffix + "min",
+            .max = prefix + mName + suffix + "max",
+            .consistent = prefix + mName + suffix + "consistent",
+            .average = prefix + mName + suffix + "average"};
+}
+
+void LayerInfoV2::RefreshRateHistory::clear() {
+    mRefreshRates.clear();
+}
+
+bool LayerInfoV2::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
+    mRefreshRates.push_back({refreshRate, now});
+    while (mRefreshRates.size() >= HISTORY_SIZE ||
+           now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
+        mRefreshRates.pop_front();
+    }
+
+    if (CC_UNLIKELY(sTraceEnabled)) {
+        if (!mHeuristicTraceTagData.has_value()) {
+            mHeuristicTraceTagData = makeHeuristicTraceTagData();
+        }
+
+        ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
+    }
+
+    return isConsistent();
+}
+
+bool LayerInfoV2::RefreshRateHistory::isConsistent() const {
+    if (mRefreshRates.empty()) return true;
+
+    const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
+    const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
+    const auto consistent = max->refreshRate - min->refreshRate <= MARGIN_FPS;
+
+    if (CC_UNLIKELY(sTraceEnabled)) {
+        if (!mHeuristicTraceTagData.has_value()) {
+            mHeuristicTraceTagData = makeHeuristicTraceTagData();
+        }
+
+        ATRACE_INT(mHeuristicTraceTagData->max.c_str(), static_cast<int>(max->refreshRate));
+        ATRACE_INT(mHeuristicTraceTagData->min.c_str(), static_cast<int>(min->refreshRate));
+        ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
+    }
+
+    return consistent;
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.h b/services/surfaceflinger/Scheduler/LayerInfoV2.h
index 9e6783f..33dc66f 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.h
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.h
@@ -56,6 +56,12 @@
     friend class LayerHistoryTestV2;
 
 public:
+    static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
+
+    static void setRefreshRateConfigs(const RefreshRateConfigs& refreshRateConfigs) {
+        sRefreshRateConfigs = &refreshRateConfigs;
+    }
+
     LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
                 LayerHistory::LayerVoteType defaultVote);
 
@@ -86,6 +92,9 @@
     // updated time, the updated time is the present time.
     nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
 
+    // Returns a C string for tracing a vote
+    const char* getTraceTag(LayerHistory::LayerVoteType type) const;
+
     void onLayerInactive(nsecs_t now) {
         // Mark mFrameTimeValidSince to now to ignore all previous frame times.
         // We are not deleting the old frame to keep track of whether we should treat the first
@@ -93,7 +102,8 @@
         // posting infrequent updates.
         const auto timePoint = std::chrono::nanoseconds(now);
         mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
-        mLastReportedRefreshRate = 0.0f;
+        mLastRefreshRate = {};
+        mRefreshRateHistory.clear();
     }
 
     void clearHistory(nsecs_t now) {
@@ -109,12 +119,73 @@
         bool pendingConfigChange;
     };
 
+    // Holds information about the calculated and reported refresh rate
+    struct RefreshRateHeuristicData {
+        // Rate calculated on the layer
+        float calculated = 0.0f;
+        // Last reported rate for LayerInfoV2::getRefreshRate()
+        float reported = 0.0f;
+        // Whether the last reported rate for LayerInfoV2::getRefreshRate()
+        // was due to animation or infrequent updates
+        bool animatingOrInfrequent = false;
+    };
+
+    // Holds information about the layer vote
+    struct LayerVote {
+        LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
+        float fps = 0.0f;
+    };
+
+    // Class to store past calculated refresh rate and determine whether
+    // the refresh rate calculated is consistent with past values
+    class RefreshRateHistory {
+    public:
+        static constexpr auto HISTORY_SIZE = 90;
+        static constexpr std::chrono::nanoseconds HISTORY_DURATION = 2s;
+
+        RefreshRateHistory(const std::string& name) : mName(name) {}
+
+        // Clears History
+        void clear();
+
+        // Adds a new refresh rate and returns true if it is consistent
+        bool add(float refreshRate, nsecs_t now);
+
+    private:
+        friend class LayerHistoryTestV2;
+
+        // Holds the refresh rate when it was calculated
+        struct RefreshRateData {
+            float refreshRate = 0.0f;
+            nsecs_t timestamp = 0;
+
+            bool operator<(const RefreshRateData& other) const {
+                return refreshRate < other.refreshRate;
+            }
+        };
+
+        // Holds tracing strings
+        struct HeuristicTraceTagData {
+            std::string min;
+            std::string max;
+            std::string consistent;
+            std::string average;
+        };
+
+        bool isConsistent() const;
+        HeuristicTraceTagData makeHeuristicTraceTagData() const;
+
+        const std::string mName;
+        mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
+        std::deque<RefreshRateData> mRefreshRates;
+        static constexpr float MARGIN_FPS = 1.0;
+    };
+
     bool isFrequent(nsecs_t now) const;
     bool isAnimating(nsecs_t now) const;
     bool hasEnoughDataForHeuristic() const;
-    std::optional<float> calculateRefreshRateIfPossible();
-    std::pair<nsecs_t, bool> calculateAverageFrameTime() const;
-    bool isRefreshRateStable(nsecs_t averageFrameTime, bool missingPresentTime) const;
+    std::optional<float> calculateRefreshRateIfPossible(nsecs_t now);
+    std::optional<nsecs_t> calculateAverageFrameTime() const;
     bool isFrameTimeValid(const FrameTimeData&) const;
 
     const std::string mName;
@@ -123,23 +194,27 @@
     const nsecs_t mHighRefreshRatePeriod;
     LayerHistory::LayerVoteType mDefaultVote;
 
+    LayerVote mLayerVote;
+
     nsecs_t mLastUpdatedTime = 0;
 
     nsecs_t mLastAnimationTime = 0;
 
-    float mLastReportedRefreshRate = 0.0f;
-
-    // Holds information about the layer vote
-    struct {
-        LayerHistory::LayerVoteType type;
-        float fps;
-    } mLayerVote;
+    RefreshRateHeuristicData mLastRefreshRate;
 
     std::deque<FrameTimeData> mFrameTimes;
     std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
             std::chrono::steady_clock::now();
-    static constexpr size_t HISTORY_SIZE = 90;
-    static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
+    static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
+    static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
+
+    RefreshRateHistory mRefreshRateHistory;
+
+    mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;
+
+    // Shared for all LayerInfo instances
+    static const RefreshRateConfigs* sRefreshRateConfigs;
+    static bool sTraceEnabled;
 };
 
 } // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 3c8bd68..ea27955 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -242,7 +242,7 @@
 
             if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
                 layer.vote == LayerVoteType::Heuristic) {
-                const auto layerScore = [&]() {
+                const auto layerScore = [&] {
                     // Calculate how many display vsyncs we need to present a single frame for this
                     // layer
                     const auto [displayFramesQuot, displayFramesRem] =
@@ -384,7 +384,8 @@
 
 RefreshRateConfigs::RefreshRateConfigs(
         const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
-        HwcConfigIndexType currentConfigId) {
+        HwcConfigIndexType currentConfigId)
+      : mKnownFrameRates(constructKnownFrameRates(configs)) {
     LOG_ALWAYS_FATAL_IF(configs.empty());
     LOG_ALWAYS_FATAL_IF(currentConfigId.value() >= configs.size());
 
@@ -544,4 +545,40 @@
                        &mAppRequestRefreshRates);
 }
 
+std::vector<float> RefreshRateConfigs::constructKnownFrameRates(
+        const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs) {
+    std::vector<float> knownFrameRates = {24.0f, 30.0f, 45.0f, 60.0f, 72.0f};
+    knownFrameRates.reserve(knownFrameRates.size() + configs.size());
+
+    // Add all supported refresh rates to the set
+    for (const auto& config : configs) {
+        const auto refreshRate = 1e9f / config->getVsyncPeriod();
+        knownFrameRates.emplace_back(refreshRate);
+    }
+
+    // Sort and remove duplicates
+    const auto frameRatesEqual = [](float a, float b) { return std::abs(a - b) <= 0.01f; };
+    std::sort(knownFrameRates.begin(), knownFrameRates.end());
+    knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
+                                      frameRatesEqual),
+                          knownFrameRates.end());
+    return knownFrameRates;
+}
+
+float RefreshRateConfigs::findClosestKnownFrameRate(float frameRate) const {
+    if (frameRate <= *mKnownFrameRates.begin()) {
+        return *mKnownFrameRates.begin();
+    }
+
+    if (frameRate >= *std::prev(mKnownFrameRates.end())) {
+        return *std::prev(mKnownFrameRates.end());
+    }
+
+    auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate);
+
+    const auto distance1 = std::abs(frameRate - *lowerBound);
+    const auto distance2 = std::abs(frameRate - *std::prev(lowerBound));
+    return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound);
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index ff1eabd..88e4eb5 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -29,7 +29,6 @@
 #include "Scheduler/StrongTyping.h"
 
 namespace android::scheduler {
-class RefreshRateConfigsTest;
 
 using namespace std::chrono_literals;
 
@@ -87,7 +86,7 @@
 
     private:
         friend RefreshRateConfigs;
-        friend RefreshRateConfigsTest;
+        friend class RefreshRateConfigsTest;
 
         // The tolerance within which we consider FPS approximately equals.
         static constexpr float FPS_EPSILON = 0.001f;
@@ -258,11 +257,18 @@
     // Returns a string that represents the layer vote type
     static std::string layerVoteTypeString(LayerVoteType vote);
 
+    // Returns a known frame rate that is the closest to frameRate
+    float findClosestKnownFrameRate(float frameRate) const;
+
     RefreshRateConfigs(const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
                        HwcConfigIndexType currentConfigId);
 
 private:
+    friend class RefreshRateConfigsTest;
+
     void constructAvailableRefreshRates() REQUIRES(mLock);
+    static std::vector<float> constructKnownFrameRates(
+            const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs);
 
     void getSortedRefreshRateList(
             const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
@@ -320,6 +326,10 @@
     const RefreshRate* mMaxSupportedRefreshRate;
 
     mutable std::mutex mLock;
+
+    // A sorted list of known frame rates that a Heuristic layer will choose
+    // from based on the closest value.
+    const std::vector<float> mKnownFrameRates;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index e250bbf..bfa7493 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -111,7 +111,7 @@
     using namespace sysprop;
 
     if (mUseContentDetectionV2) {
-        mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+        mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>(refreshRateConfig);
     } else {
         mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
     }
diff --git a/services/surfaceflinger/Scheduler/VSyncModulator.cpp b/services/surfaceflinger/Scheduler/VSyncModulator.cpp
index 40a992c..510dc2d 100644
--- a/services/surfaceflinger/Scheduler/VSyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncModulator.cpp
@@ -25,6 +25,7 @@
 #include <cutils/properties.h>
 #include <utils/Trace.h>
 
+#include <chrono>
 #include <cinttypes>
 #include <mutex>
 
@@ -52,6 +53,7 @@
 void VSyncModulator::setTransactionStart(Scheduler::TransactionStart transactionStart) {
     if (transactionStart == Scheduler::TransactionStart::EARLY) {
         mRemainingEarlyFrameCount = MIN_EARLY_FRAME_COUNT_TRANSACTION;
+        mEarlyTxnStartTime = std::chrono::steady_clock::now();
     }
 
     // An early transaction stays an early transaction.
@@ -64,6 +66,7 @@
 }
 
 void VSyncModulator::onTransactionHandled() {
+    mTxnAppliedTime = std::chrono::steady_clock::now();
     if (mTransactionStart == Scheduler::TransactionStart::NORMAL) return;
     mTransactionStart = Scheduler::TransactionStart::NORMAL;
     updateOffsets();
@@ -87,9 +90,16 @@
 
 void VSyncModulator::onRefreshed(bool usedRenderEngine) {
     bool updateOffsetsNeeded = false;
-    if (mRemainingEarlyFrameCount > 0) {
-        mRemainingEarlyFrameCount--;
-        updateOffsetsNeeded = true;
+
+    // Apply a 1ms margin to account for potential data races
+    // This might make us stay in early offsets for one
+    // additional frame but it's better to be conservative here.
+    static const constexpr std::chrono::nanoseconds kMargin = 1ms;
+    if ((mEarlyTxnStartTime.load() + kMargin) < mTxnAppliedTime.load()) {
+        if (mRemainingEarlyFrameCount > 0) {
+            mRemainingEarlyFrameCount--;
+            updateOffsetsNeeded = true;
+        }
     }
     if (usedRenderEngine) {
         mRemainingRenderEngineUsageCount = MIN_EARLY_GL_FRAME_COUNT_TRANSACTION;
diff --git a/services/surfaceflinger/Scheduler/VSyncModulator.h b/services/surfaceflinger/Scheduler/VSyncModulator.h
index 704a5d5..d777ef9 100644
--- a/services/surfaceflinger/Scheduler/VSyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VSyncModulator.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <chrono>
 #include <mutex>
 
 #include "Scheduler.h"
@@ -110,6 +111,8 @@
     std::atomic<bool> mRefreshRateChangePending = false;
     std::atomic<int> mRemainingEarlyFrameCount = 0;
     std::atomic<int> mRemainingRenderEngineUsageCount = 0;
+    std::atomic<std::chrono::steady_clock::time_point> mEarlyTxnStartTime = {};
+    std::atomic<std::chrono::steady_clock::time_point> mTxnAppliedTime = {};
 
     bool mTraceDetailedInfo = false;
 };
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 16d102c..c743de0 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -278,7 +278,9 @@
         return false;
     }
 
-    if (mSupportKernelIdleTimer) {
+    const bool periodIsChanging =
+            mPeriodTransitioningTo && (*mPeriodTransitioningTo != getPeriod());
+    if (mSupportKernelIdleTimer && !periodIsChanging) {
         // Clear out the Composer-provided period and use the allowance logic below
         HwcVsyncPeriod = {};
     }
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 9152571..51cf77e 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1931,6 +1931,7 @@
     // ...but if it's larger than 1s then we missed the trace cutoff.
     static constexpr nsecs_t kMaxJankyDuration =
             std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
+    nsecs_t jankDurationToUpload = -1;
     // If we're in a user build then don't push any atoms
     if (!mIsUserBuild && mMissedFrameJankCount > 0) {
         const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
@@ -1942,10 +1943,7 @@
             const nsecs_t currentTime = systemTime();
             const nsecs_t jankDuration = currentTime - mMissedFrameJankStart;
             if (jankDuration > kMinJankyDuration && jankDuration < kMaxJankyDuration) {
-                ATRACE_NAME("Jank detected");
-                const int32_t jankyDurationMillis = jankDuration / (1000 * 1000);
-                android::util::stats_write(android::util::DISPLAY_JANK_REPORTED,
-                                           jankyDurationMillis, mMissedFrameJankCount);
+                jankDurationToUpload = jankDuration;
             }
 
             // We either reported a jank event or we missed the trace
@@ -1998,6 +1996,7 @@
 
     refreshNeeded |= mRepaintEverything;
     if (refreshNeeded && CC_LIKELY(mBootStage != BootStage::BOOTLOADER)) {
+        mLastJankDuration = jankDurationToUpload;
         // Signal a refresh if a transaction modified the window state,
         // a new buffer was latched, or if HWC has requested a full
         // repaint
@@ -2304,6 +2303,14 @@
     const size_t appConnections = mScheduler->getEventThreadConnectionCount(mAppConnectionHandle);
     mTimeStats->recordDisplayEventConnectionCount(sfConnections + appConnections);
 
+    if (mLastJankDuration > 0) {
+        ATRACE_NAME("Jank detected");
+        const int32_t jankyDurationMillis = mLastJankDuration / (1000 * 1000);
+        android::util::stats_write(android::util::DISPLAY_JANK_REPORTED, jankyDurationMillis,
+                                   mMissedFrameJankCount);
+        mLastJankDuration = -1;
+    }
+
     if (isDisplayConnected && !display->isPoweredOn()) {
         return;
     }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index f5be8a8..e7bfdc7 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1287,6 +1287,8 @@
     // janky frames there are.
     nsecs_t mMissedFrameJankStart = 0;
     int32_t mMissedFrameJankCount = 0;
+    // Positive if jank should be uploaded in postComposition
+    nsecs_t mLastJankDuration = -1;
 
     int mFrameRateFlexibilityTokenCount = 0;
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
index f376b4a..afd2b71 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
@@ -38,7 +38,9 @@
     static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfoV2::HISTORY_SIZE;
     static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfoV2::MAX_FREQUENT_LAYER_PERIOD_NS;
     static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfoV2::FREQUENT_LAYER_WINDOW_SIZE;
-    static constexpr auto PRESENT_TIME_HISTORY_TIME = LayerInfoV2::HISTORY_TIME;
+    static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfoV2::HISTORY_DURATION;
+    static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
+            LayerInfoV2::RefreshRateHistory::HISTORY_DURATION;
 
     static constexpr float LO_FPS = 30.f;
     static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
@@ -84,6 +86,23 @@
         return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger(), std::move(name)));
     }
 
+    void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, float frameRate,
+                               float desiredRefreshRate, int numFrames) {
+        const nsecs_t framePeriod = static_cast<nsecs_t>(1e9f / frameRate);
+        impl::LayerHistoryV2::Summary summary;
+        for (int i = 0; i < numFrames; i++) {
+            history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+            time += framePeriod;
+
+            summary = history().summarize(time);
+        }
+
+        ASSERT_EQ(1, summary.size());
+        ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+        ASSERT_FLOAT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate)
+                << "Frame rate is " << frameRate;
+    }
+
     Hwc2::mock::Display mDisplay;
     RefreshRateConfigs mConfigs{{HWC2::Display::Config::Builder(mDisplay, 0)
                                          .setVsyncPeriod(int32_t(LO_FPS_PERIOD))
@@ -346,14 +365,17 @@
     EXPECT_EQ(0, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 
+    impl::LayerHistoryV2::Summary summary;
+
     // layer1 is active but infrequent.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+        summary = history().summarize(time);
     }
 
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    ASSERT_EQ(1, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 
@@ -361,28 +383,30 @@
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
     // layer1 is still active but infrequent.
     history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
 
-    ASSERT_EQ(2, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
-    ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
+    ASSERT_EQ(2, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
+    ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
     EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
     // layer1 is no longer active.
     // layer2 is frequent and has low refresh rate.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+    for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += LO_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    ASSERT_EQ(1, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -396,33 +420,36 @@
 
         history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
-    ASSERT_EQ(2, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
-    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[1].vote);
+    ASSERT_EQ(2, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 
     // layer3 becomes recently active.
     history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-    ASSERT_EQ(2, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+    summary = history().summarize(time);
+    ASSERT_EQ(2, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 
     // layer1 expires.
     layer1.clear();
-    ASSERT_EQ(2, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+    summary = history().summarize(time);
+    ASSERT_EQ(2, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
     EXPECT_EQ(2, layerCount());
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -432,37 +459,41 @@
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += LO_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    ASSERT_EQ(1, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
     // layer2 expires.
     layer2.clear();
-    EXPECT_TRUE(history().summarize(time).empty());
+    summary = history().summarize(time);
+    EXPECT_TRUE(summary.empty());
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 
     // layer3 becomes active and has high refresh rate.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
         history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[0].desiredRefreshRate);
+    ASSERT_EQ(1, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
     // layer3 expires.
     layer3.clear();
-    EXPECT_TRUE(history().summarize(time).empty());
+    summary = history().summarize(time);
+    EXPECT_TRUE(summary.empty());
     EXPECT_EQ(0, layerCount());
     EXPECT_EQ(0, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
@@ -600,6 +631,46 @@
     EXPECT_EQ(1, animatingLayerCount(time));
 }
 
+TEST_F(LayerHistoryTestV2, heuristicLayer60Hz) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+    for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
+        recordFramesAndExpect(layer, time, fps, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+    }
+}
+
+TEST_F(LayerHistoryTestV2, heuristicLayer60_30Hz) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+
+    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 30.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 30.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 60.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+}
+
+TEST_F(LayerHistoryTestV2, heuristicLayerNotOscillating) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+
+    recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.90f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.00f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.90f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+}
+
 class LayerHistoryTestV2Parameterized
       : public LayerHistoryTestV2,
         public testing::WithParamInterface<std::chrono::nanoseconds> {};
@@ -642,7 +713,7 @@
             infrequentLayerUpdates++;
         }
 
-        if (time - startTime > PRESENT_TIME_HISTORY_TIME.count()) {
+        if (time - startTime > PRESENT_TIME_HISTORY_DURATION.count()) {
             ASSERT_NE(0, history().summarize(time).size());
             ASSERT_GE(2, history().summarize(time).size());
 
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index c919e93..4fa8455 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -43,6 +43,14 @@
     RefreshRateConfigsTest();
     ~RefreshRateConfigsTest();
 
+    float findClosestKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs, float frameRate) {
+        return refreshRateConfigs.findClosestKnownFrameRate(frameRate);
+    }
+
+    std::vector<float> getKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs) {
+        return refreshRateConfigs.mKnownFrameRates;
+    }
+
     // Test config IDs
     static inline const HwcConfigIndexType HWC_CONFIG_ID_60 = HwcConfigIndexType(0);
     static inline const HwcConfigIndexType HWC_CONFIG_ID_90 = HwcConfigIndexType(1);
@@ -902,8 +910,7 @@
         const auto& refreshRate =
                 refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
                                                        /*idle*/ false, &ignored);
-        printf("%.2fHz chooses %s\n", fps, refreshRate.getName().c_str());
-        EXPECT_EQ(mExpected60Config, refreshRate);
+        EXPECT_EQ(mExpected60Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
     }
 }
 
@@ -989,8 +996,7 @@
         const auto& refreshRate =
                 refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
                                                        /*idle*/ false, &ignored);
-        printf("%.2fHz chooses %s\n", fps, refreshRate.getName().c_str());
-        EXPECT_EQ(mExpected90Config, refreshRate);
+        EXPECT_EQ(mExpected90Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
     }
 }
 
@@ -1456,6 +1462,73 @@
                       .getConfigId());
 }
 
+TEST_F(RefreshRateConfigsTest, findClosestKnownFrameRate) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m60_90Device,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
+        const auto knownFrameRate = findClosestKnownFrameRate(*refreshRateConfigs, fps);
+        float expectedFrameRate;
+        if (fps < 26.91f) {
+            expectedFrameRate = 24.0f;
+        } else if (fps < 37.51f) {
+            expectedFrameRate = 30.0f;
+        } else if (fps < 52.51f) {
+            expectedFrameRate = 45.0f;
+        } else if (fps < 66.01f) {
+            expectedFrameRate = 60.0f;
+        } else if (fps < 81.01f) {
+            expectedFrameRate = 72.0f;
+        } else {
+            expectedFrameRate = 90.0f;
+        }
+        EXPECT_FLOAT_EQ(expectedFrameRate, knownFrameRate)
+                << "findClosestKnownFrameRate(" << fps << ") = " << knownFrameRate;
+    }
+}
+
+TEST_F(RefreshRateConfigsTest, getBestRefreshRate_KnownFrameRate) {
+    bool ignored;
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m60_90Device,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    struct ExpectedRate {
+        float rate;
+        const RefreshRate& expected;
+    };
+
+    /* clang-format off */
+    std::vector<ExpectedRate> knownFrameRatesExpectations = {
+        {24.0f, mExpected60Config},
+        {30.0f, mExpected60Config},
+        {45.0f, mExpected90Config},
+        {60.0f, mExpected60Config},
+        {72.0f, mExpected90Config},
+        {90.0f, mExpected90Config},
+    };
+    /* clang-format on */
+
+    // Make sure the test tests all the known frame rate
+    const auto knownFrameRateList = getKnownFrameRate(*refreshRateConfigs);
+    const auto equal = std::equal(knownFrameRateList.begin(), knownFrameRateList.end(),
+                                  knownFrameRatesExpectations.begin(),
+                                  [](float a, const ExpectedRate& b) { return a == b.rate; });
+    EXPECT_TRUE(equal);
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::Heuristic;
+    for (const auto& expectedRate : knownFrameRatesExpectations) {
+        layer.desiredRefreshRate = expectedRate.rate;
+        const auto& refreshRate = refreshRateConfigs->getBestRefreshRate(layers,
+                                                                         /*touchActive*/ false,
+                                                                         /*idle*/ false, &ignored);
+        EXPECT_EQ(expectedRate.expected, refreshRate);
+    }
+}
+
 } // namespace
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 41b5d49..d5ecae8 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -31,7 +31,7 @@
     TestableScheduler(const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
           : Scheduler([](bool) {}, configs, *this, useContentDetectionV2, true) {
         if (mUseContentDetectionV2) {
-            mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>(configs);
         } else {
             mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
         }
@@ -43,7 +43,7 @@
           : Scheduler(std::move(primaryDispSync), std::move(eventControlThread), configs, *this,
                       useContentDetectionV2, true) {
         if (mUseContentDetectionV2) {
-            mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>(configs);
         } else {
             mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
         }
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index ccbd17f..a972562 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -672,15 +672,30 @@
                                     kPendingLimit, true /* supportKernelIdleTimer */);
 
     bool periodFlushed = true;
-    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(2);
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(5);
     idleReactor.setIgnorePresentFences(true);
 
-    nsecs_t const newPeriod = 5000;
-    idleReactor.setPeriod(newPeriod);
-
+    // First, set the same period, which should only be confirmed when we receive two
+    // matching callbacks
+    idleReactor.setPeriod(10000);
     EXPECT_TRUE(idleReactor.addResyncSample(0, 0, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
-    EXPECT_FALSE(idleReactor.addResyncSample(newPeriod, 0, &periodFlushed));
+    // Correct period but incorrect timestamp delta
+    EXPECT_TRUE(idleReactor.addResyncSample(0, 10000, &periodFlushed));
+    EXPECT_FALSE(periodFlushed);
+    // Correct period and correct timestamp delta
+    EXPECT_FALSE(idleReactor.addResyncSample(10000, 10000, &periodFlushed));
+    EXPECT_TRUE(periodFlushed);
+
+    // Then, set a new period, which should be confirmed as soon as we receive a callback
+    // reporting the new period
+    nsecs_t const newPeriod = 5000;
+    idleReactor.setPeriod(newPeriod);
+    // Incorrect timestamp delta and period
+    EXPECT_TRUE(idleReactor.addResyncSample(20000, 10000, &periodFlushed));
+    EXPECT_FALSE(periodFlushed);
+    // Incorrect timestamp delta but correct period
+    EXPECT_FALSE(idleReactor.addResyncSample(20000, 5000, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
 
     EXPECT_TRUE(idleReactor.addPresentFence(generateSignalledFenceWithTime(0)));