Merge "Remove __ANDROID_API__ guards"
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
index bfcc058..ba25a5a 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -39,8 +39,13 @@
     std::string calling_package;
 };
 
-static binder::Status exception(uint32_t code, const std::string& msg) {
-    MYLOGE("%s (%d) ", msg.c_str(), code);
+static binder::Status exception(uint32_t code, const std::string& msg,
+                                const std::string& extra_msg = "") {
+    if (extra_msg.empty()) {
+        MYLOGE("%s (%d) ", msg.c_str(), code);
+    } else {
+        MYLOGE("%s %s (%d) ", msg.c_str(), extra_msg.c_str(), code);
+    }
     return binder::Status::fromExceptionCode(code, String8(msg.c_str()));
 }
 
@@ -60,7 +65,7 @@
 
 }  // namespace
 
-DumpstateService::DumpstateService() : ds_(nullptr) {
+DumpstateService::DumpstateService() : ds_(nullptr), calling_uid_(-1), calling_package_() {
 }
 
 char const* DumpstateService::getServiceName() {
@@ -131,6 +136,10 @@
     ds_->SetOptions(std::move(options));
     ds_->listener_ = listener;
 
+    // Track caller info for cancellation purposes.
+    calling_uid_ = calling_uid;
+    calling_package_ = calling_package;
+
     DumpstateInfo* ds_info = new DumpstateInfo();
     ds_info->ds = ds_;
     ds_info->calling_uid = calling_uid;
@@ -149,8 +158,20 @@
     return binder::Status::ok();
 }
 
-binder::Status DumpstateService::cancelBugreport() {
+binder::Status DumpstateService::cancelBugreport(int32_t calling_uid,
+                                                 const std::string& calling_package) {
     std::lock_guard<std::mutex> lock(lock_);
+    if (calling_uid != calling_uid_ || calling_package != calling_package_) {
+        // Note: we use a SecurityException to prevent BugreportManagerServiceImpl from killing the
+        // report in progress (from another caller).
+        return exception(
+            binder::Status::EX_SECURITY,
+            StringPrintf("Cancellation requested by %d/%s does not match report in "
+                         "progress",
+                         calling_uid, calling_package.c_str()),
+            // Sharing the owner of the BR is a (minor) leak, so leave it out of the app's exception
+            StringPrintf("started by %d/%s", calling_uid_, calling_package_.c_str()));
+    }
     ds_->Cancel();
     return binder::Status::ok();
 }
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
index ac8d3ac..3ec8471 100644
--- a/cmds/dumpstate/DumpstateService.h
+++ b/cmds/dumpstate/DumpstateService.h
@@ -44,8 +44,7 @@
                                   const sp<IDumpstateListener>& listener,
                                   bool is_screenshot_requested) override;
 
-    // No-op
-    binder::Status cancelBugreport();
+    binder::Status cancelBugreport(int32_t calling_uid, const std::string& calling_package);
 
   private:
     // Dumpstate object which contains all the bugreporting logic.
@@ -53,6 +52,8 @@
     // one bugreport.
     // This service does not own this object.
     Dumpstate* ds_;
+    int32_t calling_uid_;
+    std::string calling_package_;
     std::mutex lock_;
 };
 
diff --git a/cmds/dumpstate/DumpstateUtil.cpp b/cmds/dumpstate/DumpstateUtil.cpp
index eeaa5a3..c833d0e 100644
--- a/cmds/dumpstate/DumpstateUtil.cpp
+++ b/cmds/dumpstate/DumpstateUtil.cpp
@@ -124,6 +124,12 @@
     return *this;
 }
 
+CommandOptions::CommandOptionsBuilder&
+CommandOptions::CommandOptionsBuilder::CloseAllFileDescriptorsOnExec() {
+    values.close_all_fds_on_exec_ = true;
+    return *this;
+}
+
 CommandOptions::CommandOptionsBuilder& CommandOptions::CommandOptionsBuilder::Log(
     const std::string& message) {
     values.logging_message_ = message;
@@ -137,6 +143,7 @@
 CommandOptions::CommandOptionsValues::CommandOptionsValues(int64_t timeout_ms)
     : timeout_ms_(timeout_ms),
       always_(false),
+      close_all_fds_on_exec_(false),
       account_mode_(DONT_DROP_ROOT),
       output_mode_(NORMAL_OUTPUT),
       logging_message_("") {
@@ -157,6 +164,10 @@
     return values.always_;
 }
 
+bool CommandOptions::ShouldCloseAllFileDescriptorsOnExec() const {
+    return values.close_all_fds_on_exec_;
+}
+
 PrivilegeMode CommandOptions::PrivilegeMode() const {
     return values.account_mode_;
 }
@@ -277,7 +288,8 @@
         MYLOGI(logging_message.c_str(), command_string.c_str());
     }
 
-    bool silent = (options.OutputMode() == REDIRECT_TO_STDERR);
+    bool silent = (options.OutputMode() == REDIRECT_TO_STDERR ||
+                   options.ShouldCloseAllFileDescriptorsOnExec());
     bool redirecting_to_fd = STDOUT_FILENO != fd;
 
     if (PropertiesHelper::IsDryRun() && !options.Always()) {
@@ -314,7 +326,27 @@
             return -1;
         }
 
-        if (silent) {
+        if (options.ShouldCloseAllFileDescriptorsOnExec()) {
+            int devnull_fd = TEMP_FAILURE_RETRY(open("/dev/null", O_RDONLY));
+            TEMP_FAILURE_RETRY(dup2(devnull_fd, STDIN_FILENO));
+            close(devnull_fd);
+            devnull_fd = TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY));
+            TEMP_FAILURE_RETRY(dup2(devnull_fd, STDOUT_FILENO));
+            TEMP_FAILURE_RETRY(dup2(devnull_fd, STDERR_FILENO));
+            close(devnull_fd);
+            // This is to avoid leaking FDs that, accidentally, have not been
+            // marked as O_CLOEXEC. Leaking FDs across exec can cause failures
+            // when execing a process that has a SELinux auto_trans rule.
+            // Here we assume that the dumpstate process didn't open more than
+            // 1000 FDs. In theory we could iterate through /proc/self/fd/, but
+            // doing that in a fork-safe way is too complex and not worth it
+            // (opendir()/readdir() do heap allocations and take locks).
+            for (int i = 0; i < 1000; i++) {
+                if (i != STDIN_FILENO && i!= STDOUT_FILENO && i != STDERR_FILENO) {
+                    close(i);
+                }
+            }
+        } else if (silent) {
             // Redirects stdout to stderr
             TEMP_FAILURE_RETRY(dup2(STDERR_FILENO, STDOUT_FILENO));
         } else if (redirecting_to_fd) {
diff --git a/cmds/dumpstate/DumpstateUtil.h b/cmds/dumpstate/DumpstateUtil.h
index b099443..b00c46e 100644
--- a/cmds/dumpstate/DumpstateUtil.h
+++ b/cmds/dumpstate/DumpstateUtil.h
@@ -80,6 +80,7 @@
 
         int64_t timeout_ms_;
         bool always_;
+        bool close_all_fds_on_exec_;
         PrivilegeMode account_mode_;
         OutputMode output_mode_;
         std::string logging_message_;
@@ -112,6 +113,13 @@
         CommandOptionsBuilder& DropRoot();
         /* Sets the command's OutputMode as `REDIRECT_TO_STDERR` */
         CommandOptionsBuilder& RedirectStderr();
+        /* Closes all file descriptors before exec-ing the target process. This
+         * includes also stdio pipes, which are dup-ed on /dev/null. It prevents
+         * leaking opened FDs to the target process, which in turn can hit
+         * selinux denials in presence of auto_trans rules.
+         */
+        CommandOptionsBuilder& CloseAllFileDescriptorsOnExec();
+
         /* When not empty, logs a message before executing the command.
          * Must contain a `%s`, which will be replaced by the full command line, and end on `\n`. */
         CommandOptionsBuilder& Log(const std::string& message);
@@ -130,6 +138,8 @@
     int64_t TimeoutInMs() const;
     /* Checks whether the command should always be run, even on dry-run mode. */
     bool Always() const;
+    /* Checks whether all FDs should be closed prior to the exec() calls. */
+    bool ShouldCloseAllFileDescriptorsOnExec() const;
     /** Gets the PrivilegeMode of the command. */
     PrivilegeMode PrivilegeMode() const;
     /** Gets the OutputMode of the command. */
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
index ba008bb..0793f0b 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2016, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,9 +19,9 @@
 import android.os.IDumpstateListener;
 
 /**
-  * Binder interface for the currently running dumpstate process.
-  * {@hide}
-  */
+ * Binder interface for the currently running dumpstate process.
+ * {@hide}
+ */
 interface IDumpstate {
 
     // NOTE: If you add to or change these modes, please also change the corresponding enums
@@ -49,10 +49,10 @@
     // Default mode.
     const int BUGREPORT_MODE_DEFAULT = 6;
 
-    /*
+    /**
      * Starts a bugreport in the background.
      *
-     *<p>Shows the user a dialog to get consent for sharing the bugreport with the calling
+     * <p>Shows the user a dialog to get consent for sharing the bugreport with the calling
      * application. If they deny {@link IDumpstateListener#onError} will be called. If they
      * consent and bugreport generation is successful artifacts will be copied to the given fds and
      * {@link IDumpstateListener#onFinished} will be called. If there
@@ -71,8 +71,15 @@
                         int bugreportMode, IDumpstateListener listener,
                         boolean isScreenshotRequested);
 
-    /*
+    /**
      * Cancels the bugreport currently in progress.
+     *
+     * <p>The caller must match the original caller of {@link #startBugreport} in order for the
+     * report to actually be cancelled. A {@link SecurityException} is reported if a mismatch is
+     * detected.
+     *
+     * @param callingUid UID of the original application that requested the cancellation.
+     * @param callingPackage package of the original application that requested the cancellation.
      */
-    void cancelBugreport();
+    void cancelBugreport(int callingUid, @utf8InCpp String callingPackage);
 }
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 6cb8d83..4a4a510 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -175,6 +175,7 @@
 #define SNAPSHOTCTL_LOG_DIR "/data/misc/snapshotctl_log"
 #define LINKERCONFIG_DIR "/linkerconfig"
 #define PACKAGE_DEX_USE_LIST "/data/system/package-dex-usage.list"
+#define SYSTEM_TRACE_SNAPSHOT "/data/misc/perfetto-traces/bugreport/systrace.pftrace"
 
 // TODO(narayan): Since this information has to be kept in sync
 // with tombstoned, we should just put it in a common header.
@@ -1053,6 +1054,24 @@
     }
 }
 
+static void MaybeAddSystemTraceToZip() {
+    // This function copies into the .zip the system trace that was snapshotted
+    // by the early call to MaybeSnapshotSystemTrace(), if any background
+    // tracing was happening.
+    if (!ds.IsZipping()) {
+        MYLOGD("Not dumping system trace because it's not a zipped bugreport\n");
+        return;
+    }
+    if (!ds.has_system_trace_) {
+        // No background trace was happening at the time dumpstate was invoked.
+        return;
+    }
+    ds.AddZipEntry(
+            ZIP_ROOT_DIR + SYSTEM_TRACE_SNAPSHOT,
+            SYSTEM_TRACE_SNAPSHOT);
+    android::os::UnlinkAndLogOnError(SYSTEM_TRACE_SNAPSHOT);
+}
+
 static void DumpVisibleWindowViews() {
     if (!ds.IsZipping()) {
         MYLOGD("Not dumping visible views because it's not a zipped bugreport\n");
@@ -1650,6 +1669,8 @@
 
     AddAnrTraceFiles();
 
+    MaybeAddSystemTraceToZip();
+
     // NOTE: tombstones are always added as separate entries in the zip archive
     // and are not interspersed with the main report.
     const bool tombstones_dumped = AddDumps(ds.tombstone_data_.begin(), ds.tombstone_data_.end(),
@@ -2887,6 +2908,13 @@
         RunDumpsysCritical();
     }
     MaybeTakeEarlyScreenshot();
+
+    if (!is_dumpstate_restricted) {
+        // Snapshot the system trace now (if running) to avoid that dumpstate's
+        // own activity pushes out interesting data from the trace ring buffer.
+        // The trace file is added to the zip by MaybeAddSystemTraceToZip().
+        MaybeSnapshotSystemTrace();
+    }
     onUiIntensiveBugreportDumpsFinished(calling_uid);
     MaybeCheckUserConsent(calling_uid, calling_package);
     if (options_->telephony_only) {
@@ -2977,6 +3005,26 @@
     TakeScreenshot();
 }
 
+void Dumpstate::MaybeSnapshotSystemTrace() {
+    // If a background system trace is happening and is marked as "suitable for
+    // bugreport" (i.e. bugreport_score > 0 in the trace config), this command
+    // will stop it and serialize into SYSTEM_TRACE_SNAPSHOT. In the (likely)
+    // case that no trace is ongoing, this command is a no-op.
+    // Note: this should not be enqueued as we need to freeze the trace before
+    // dumpstate starts. Otherwise the trace ring buffers will contain mostly
+    // the dumpstate's own activity which is irrelevant.
+    int res = RunCommand(
+        "SERIALIZE PERFETTO TRACE",
+        {"perfetto", "--save-for-bugreport"},
+        CommandOptions::WithTimeout(10)
+            .DropRoot()
+            .CloseAllFileDescriptorsOnExec()
+            .Build());
+    has_system_trace_ = res == 0;
+    // MaybeAddSystemTraceToZip() will take care of copying the trace in the zip
+    // file in the later stages.
+}
+
 void Dumpstate::onUiIntensiveBugreportDumpsFinished(int32_t calling_uid) {
     if (calling_uid == AID_SHELL || !CalledByApi()) {
         return;
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 255243f..f83968b 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -458,6 +458,11 @@
     // Whether it should take an screenshot earlier in the process.
     bool do_early_screenshot_ = false;
 
+    // This is set to true when the trace snapshot request in the early call to
+    // MaybeSnapshotSystemTrace(). When this is true, the later stages of
+    // dumpstate will append the trace to the zip archive.
+    bool has_system_trace_ = false;
+
     std::unique_ptr<Progress> progress_;
 
     // When set, defines a socket file-descriptor use to report progress to bugreportz
@@ -543,6 +548,7 @@
     RunStatus DumpstateDefaultAfterCritical();
 
     void MaybeTakeEarlyScreenshot();
+    void MaybeSnapshotSystemTrace();
 
     void onUiIntensiveBugreportDumpsFinished(int32_t calling_uid);
 
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 1bba5e4..e82f0cc 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -618,29 +618,31 @@
     std::mutex mMutex;
 };
 
+void Surface::getDequeueBufferInputLocked(
+        IGraphicBufferProducer::DequeueBufferInput* dequeueInput) {
+    LOG_ALWAYS_FATAL_IF(dequeueInput == nullptr, "input is null");
+
+    dequeueInput->width = mReqWidth ? mReqWidth : mUserWidth;
+    dequeueInput->height = mReqHeight ? mReqHeight : mUserHeight;
+
+    dequeueInput->format = mReqFormat;
+    dequeueInput->usage = mReqUsage;
+
+    dequeueInput->getTimestamps = mEnableFrameTimestamps;
+}
+
 int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
     ATRACE_CALL();
     ALOGV("Surface::dequeueBuffer");
 
-    uint32_t reqWidth;
-    uint32_t reqHeight;
-    PixelFormat reqFormat;
-    uint64_t reqUsage;
-    bool enableFrameTimestamps;
-
+    IGraphicBufferProducer::DequeueBufferInput dqInput;
     {
         Mutex::Autolock lock(mMutex);
         if (mReportRemovedBuffers) {
             mRemovedBuffers.clear();
         }
 
-        reqWidth = mReqWidth ? mReqWidth : mUserWidth;
-        reqHeight = mReqHeight ? mReqHeight : mUserHeight;
-
-        reqFormat = mReqFormat;
-        reqUsage = mReqUsage;
-
-        enableFrameTimestamps = mEnableFrameTimestamps;
+        getDequeueBufferInputLocked(&dqInput);
 
         if (mSharedBufferMode && mAutoRefresh && mSharedBufferSlot !=
                 BufferItem::INVALID_BUFFER_SLOT) {
@@ -658,16 +660,17 @@
     nsecs_t startTime = systemTime();
 
     FrameEventHistoryDelta frameTimestamps;
-    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, reqWidth, reqHeight,
-                                                            reqFormat, reqUsage, &mBufferAge,
-                                                            enableFrameTimestamps ? &frameTimestamps
-                                                                                  : nullptr);
+    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, dqInput.width,
+                                                            dqInput.height, dqInput.format,
+                                                            dqInput.usage, &mBufferAge,
+                                                            dqInput.getTimestamps ?
+                                                                    &frameTimestamps : nullptr);
     mLastDequeueDuration = systemTime() - startTime;
 
     if (result < 0) {
         ALOGV("dequeueBuffer: IGraphicBufferProducer::dequeueBuffer"
                 "(%d, %d, %d, %#" PRIx64 ") failed: %d",
-                reqWidth, reqHeight, reqFormat, reqUsage, result);
+                dqInput.width, dqInput.height, dqInput.format, dqInput.usage, result);
         return result;
     }
 
@@ -696,7 +699,7 @@
         freeAllBuffers();
     }
 
-    if (enableFrameTimestamps) {
+    if (dqInput.getTimestamps) {
          mFrameEventHistory->applyDelta(frameTimestamps);
     }
 
@@ -739,6 +742,176 @@
     return OK;
 }
 
+int Surface::dequeueBuffers(std::vector<BatchBuffer>* buffers) {
+    using DequeueBufferInput = IGraphicBufferProducer::DequeueBufferInput;
+    using DequeueBufferOutput = IGraphicBufferProducer::DequeueBufferOutput;
+    using CancelBufferInput = IGraphicBufferProducer::CancelBufferInput;
+    using RequestBufferOutput = IGraphicBufferProducer::RequestBufferOutput;
+
+    ATRACE_CALL();
+    ALOGV("Surface::dequeueBuffers");
+
+    if (buffers->size() == 0) {
+        ALOGE("%s: must dequeue at least 1 buffer!", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    if (mSharedBufferMode) {
+        ALOGE("%s: batch operation is not supported in shared buffer mode!",
+                __FUNCTION__);
+        return INVALID_OPERATION;
+    }
+
+    size_t numBufferRequested = buffers->size();
+    DequeueBufferInput input;
+
+    {
+        Mutex::Autolock lock(mMutex);
+        if (mReportRemovedBuffers) {
+            mRemovedBuffers.clear();
+        }
+
+        getDequeueBufferInputLocked(&input);
+    } // Drop the lock so that we can still touch the Surface while blocking in IGBP::dequeueBuffers
+
+    std::vector<DequeueBufferInput> dequeueInput(numBufferRequested, input);
+    std::vector<DequeueBufferOutput> dequeueOutput;
+
+    nsecs_t startTime = systemTime();
+
+    status_t result = mGraphicBufferProducer->dequeueBuffers(dequeueInput, &dequeueOutput);
+
+    mLastDequeueDuration = systemTime() - startTime;
+
+    if (result < 0) {
+        ALOGV("%s: IGraphicBufferProducer::dequeueBuffers"
+                "(%d, %d, %d, %#" PRIx64 ") failed: %d",
+                __FUNCTION__, input.width, input.height, input.format, input.usage, result);
+        return result;
+    }
+
+    std::vector<CancelBufferInput> cancelBufferInputs(numBufferRequested);
+    std::vector<status_t> cancelBufferOutputs;
+    for (size_t i = 0; i < numBufferRequested; i++) {
+        cancelBufferInputs[i].slot = dequeueOutput[i].slot;
+        cancelBufferInputs[i].fence = dequeueOutput[i].fence;
+    }
+
+    for (const auto& output : dequeueOutput) {
+        if (output.result < 0) {
+            mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
+            ALOGV("%s: IGraphicBufferProducer::dequeueBuffers"
+                    "(%d, %d, %d, %#" PRIx64 ") failed: %d",
+                    __FUNCTION__, input.width, input.height, input.format, input.usage,
+                    output.result);
+            return output.result;
+        }
+
+        if (output.slot < 0 || output.slot >= NUM_BUFFER_SLOTS) {
+            mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
+            ALOGE("%s: IGraphicBufferProducer returned invalid slot number %d",
+                    __FUNCTION__, output.slot);
+            android_errorWriteLog(0x534e4554, "36991414"); // SafetyNet logging
+            return FAILED_TRANSACTION;
+        }
+
+        if (input.getTimestamps && !output.timestamps.has_value()) {
+            mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
+            ALOGE("%s: no frame timestamp returns!", __FUNCTION__);
+            return FAILED_TRANSACTION;
+        }
+
+        // this should never happen
+        ALOGE_IF(output.fence == nullptr,
+                "%s: received null Fence! slot=%d", __FUNCTION__, output.slot);
+    }
+
+    Mutex::Autolock lock(mMutex);
+
+    // Write this while holding the mutex
+    mLastDequeueStartTime = startTime;
+
+    std::vector<int32_t> requestBufferSlots;
+    requestBufferSlots.reserve(numBufferRequested);
+    // handle release all buffers and request buffers
+    for (const auto& output : dequeueOutput) {
+        if (output.result & IGraphicBufferProducer::RELEASE_ALL_BUFFERS) {
+            ALOGV("%s: RELEASE_ALL_BUFFERS during batch operation", __FUNCTION__);
+            freeAllBuffers();
+            break;
+        }
+    }
+
+    for (const auto& output : dequeueOutput) {
+        // Collect slots that needs requesting buffer
+        sp<GraphicBuffer>& gbuf(mSlots[output.slot].buffer);
+        if ((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuf == nullptr) {
+            if (mReportRemovedBuffers && (gbuf != nullptr)) {
+                mRemovedBuffers.push_back(gbuf);
+            }
+            requestBufferSlots.push_back(output.slot);
+        }
+    }
+
+    // Batch request Buffer
+    std::vector<RequestBufferOutput> reqBufferOutput;
+    if (requestBufferSlots.size() > 0) {
+        result = mGraphicBufferProducer->requestBuffers(requestBufferSlots, &reqBufferOutput);
+        if (result != NO_ERROR) {
+            ALOGE("%s: IGraphicBufferProducer::requestBuffers failed: %d",
+                    __FUNCTION__, result);
+            mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
+            return result;
+        }
+
+        // Check if we have any single failure
+        for (size_t i = 0; i < requestBufferSlots.size(); i++) {
+            if (reqBufferOutput[i].result != OK) {
+                ALOGE("%s: IGraphicBufferProducer::requestBuffers failed at %zu-th buffer, slot %d",
+                        __FUNCTION__, i, requestBufferSlots[i]);
+                mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
+                return reqBufferOutput[i].result;
+            }
+        }
+
+        // Fill request buffer results to mSlots
+        for (size_t i = 0; i < requestBufferSlots.size(); i++) {
+            mSlots[requestBufferSlots[i]].buffer = reqBufferOutput[i].buffer;
+        }
+    }
+
+    for (size_t batchIdx = 0; batchIdx < numBufferRequested; batchIdx++) {
+        const auto& output = dequeueOutput[batchIdx];
+        int slot = output.slot;
+        sp<GraphicBuffer>& gbuf(mSlots[slot].buffer);
+
+        if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) {
+            static FenceMonitor hwcReleaseThread("HWC release");
+            hwcReleaseThread.queueFence(output.fence);
+        }
+
+        if (input.getTimestamps) {
+             mFrameEventHistory->applyDelta(output.timestamps.value());
+        }
+
+        if (output.fence->isValid()) {
+            buffers->at(batchIdx).fenceFd = output.fence->dup();
+            if (buffers->at(batchIdx).fenceFd == -1) {
+                ALOGE("%s: error duping fence: %d", __FUNCTION__, errno);
+                // dup() should never fail; something is badly wrong. Soldier on
+                // and hope for the best; the worst that should happen is some
+                // visible corruption that lasts until the next frame.
+            }
+        } else {
+            buffers->at(batchIdx).fenceFd = -1;
+        }
+
+        buffers->at(batchIdx).buffer = gbuf.get();
+        mDequeuedSlots.insert(slot);
+    }
+    return OK;
+}
+
 int Surface::cancelBuffer(android_native_buffer_t* buffer,
         int fenceFd) {
     ATRACE_CALL();
@@ -769,15 +942,65 @@
     return OK;
 }
 
+int Surface::cancelBuffers(const std::vector<BatchBuffer>& buffers) {
+    using CancelBufferInput = IGraphicBufferProducer::CancelBufferInput;
+    ATRACE_CALL();
+    ALOGV("Surface::cancelBuffers");
+
+    if (mSharedBufferMode) {
+        ALOGE("%s: batch operation is not supported in shared buffer mode!",
+                __FUNCTION__);
+        return INVALID_OPERATION;
+    }
+
+    size_t numBuffers = buffers.size();
+    std::vector<CancelBufferInput> cancelBufferInputs(numBuffers);
+    std::vector<status_t> cancelBufferOutputs;
+    size_t numBuffersCancelled = 0;
+    int badSlotResult = 0;
+    for (size_t i = 0; i < numBuffers; i++) {
+        int slot = getSlotFromBufferLocked(buffers[i].buffer);
+        int fenceFd = buffers[i].fenceFd;
+        if (slot < 0) {
+            if (fenceFd >= 0) {
+                close(fenceFd);
+            }
+            ALOGE("%s: cannot find slot number for cancelled buffer", __FUNCTION__);
+            badSlotResult = slot;
+        } else {
+            sp<Fence> fence(fenceFd >= 0 ? new Fence(fenceFd) : Fence::NO_FENCE);
+            cancelBufferInputs[numBuffersCancelled].slot = slot;
+            cancelBufferInputs[numBuffersCancelled++].fence = fence;
+        }
+    }
+    cancelBufferInputs.resize(numBuffersCancelled);
+    mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
+
+
+    for (size_t i = 0; i < numBuffersCancelled; i++) {
+        mDequeuedSlots.erase(cancelBufferInputs[i].slot);
+    }
+
+    if (badSlotResult != 0) {
+        return badSlotResult;
+    }
+    return OK;
+}
+
 int Surface::getSlotFromBufferLocked(
         android_native_buffer_t* buffer) const {
+    if (buffer == nullptr) {
+        ALOGE("%s: input buffer is null!", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
     for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
         if (mSlots[i].buffer != nullptr &&
                 mSlots[i].buffer->handle == buffer->handle) {
             return i;
         }
     }
-    ALOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle);
+    ALOGE("%s: unknown buffer: %p", __FUNCTION__, buffer->handle);
     return BAD_VALUE;
 }
 
@@ -787,42 +1010,22 @@
     return OK;
 }
 
-int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
-    ATRACE_CALL();
-    ALOGV("Surface::queueBuffer");
-    Mutex::Autolock lock(mMutex);
-    int64_t timestamp;
+void Surface::getQueueBufferInputLocked(android_native_buffer_t* buffer, int fenceFd,
+        nsecs_t timestamp, IGraphicBufferProducer::QueueBufferInput* out) {
     bool isAutoTimestamp = false;
 
-    if (mTimestamp == NATIVE_WINDOW_TIMESTAMP_AUTO) {
+    if (timestamp == NATIVE_WINDOW_TIMESTAMP_AUTO) {
         timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
         isAutoTimestamp = true;
         ALOGV("Surface::queueBuffer making up timestamp: %.2f ms",
             timestamp / 1000000.0);
-    } else {
-        timestamp = mTimestamp;
     }
-    int i = getSlotFromBufferLocked(buffer);
-    if (i < 0) {
-        if (fenceFd >= 0) {
-            close(fenceFd);
-        }
-        return i;
-    }
-    if (mSharedBufferSlot == i && mSharedBufferHasBeenQueued) {
-        if (fenceFd >= 0) {
-            close(fenceFd);
-        }
-        return OK;
-    }
-
 
     // Make sure the crop rectangle is entirely inside the buffer.
     Rect crop(Rect::EMPTY_RECT);
     mCrop.intersect(Rect(buffer->width, buffer->height), &crop);
 
     sp<Fence> fence(fenceFd >= 0 ? new Fence(fenceFd) : Fence::NO_FENCE);
-    IGraphicBufferProducer::QueueBufferOutput output;
     IGraphicBufferProducer::QueueBufferInput input(timestamp, isAutoTimestamp,
             static_cast<android_dataspace>(mDataSpace), crop, mScalingMode,
             mTransform ^ mStickyTransform, fence, mStickyTransform,
@@ -893,15 +1096,12 @@
 
         input.setSurfaceDamage(flippedRegion);
     }
+    *out = input;
+}
 
-    nsecs_t now = systemTime();
-    status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);
-    mLastQueueDuration = systemTime() - now;
-    if (err != OK)  {
-        ALOGE("queueBuffer: error queuing buffer to SurfaceTexture, %d", err);
-    }
-
-    mDequeuedSlots.erase(i);
+void Surface::onBufferQueuedLocked(int slot, sp<Fence> fence,
+        const IGraphicBufferProducer::QueueBufferOutput& output) {
+    mDequeuedSlots.erase(slot);
 
     if (mEnableFrameTimestamps) {
         mFrameEventHistory->applyDelta(output.frameTimestamps);
@@ -935,7 +1135,7 @@
         mDirtyRegion = Region::INVALID_REGION;
     }
 
-    if (mSharedBufferMode && mAutoRefresh && mSharedBufferSlot == i) {
+    if (mSharedBufferMode && mAutoRefresh && mSharedBufferSlot == slot) {
         mSharedBufferHasBeenQueued = true;
     }
 
@@ -945,6 +1145,89 @@
         static FenceMonitor gpuCompletionThread("GPU completion");
         gpuCompletionThread.queueFence(fence);
     }
+}
+
+int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
+    ATRACE_CALL();
+    ALOGV("Surface::queueBuffer");
+    Mutex::Autolock lock(mMutex);
+
+    int i = getSlotFromBufferLocked(buffer);
+    if (i < 0) {
+        if (fenceFd >= 0) {
+            close(fenceFd);
+        }
+        return i;
+    }
+    if (mSharedBufferSlot == i && mSharedBufferHasBeenQueued) {
+        if (fenceFd >= 0) {
+            close(fenceFd);
+        }
+        return OK;
+    }
+
+    IGraphicBufferProducer::QueueBufferOutput output;
+    IGraphicBufferProducer::QueueBufferInput input;
+    getQueueBufferInputLocked(buffer, fenceFd, mTimestamp, &input);
+    sp<Fence> fence = input.fence;
+
+    nsecs_t now = systemTime();
+    status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);
+    mLastQueueDuration = systemTime() - now;
+    if (err != OK)  {
+        ALOGE("queueBuffer: error queuing buffer, %d", err);
+    }
+
+    onBufferQueuedLocked(i, fence, output);
+    return err;
+}
+
+int Surface::queueBuffers(const std::vector<BatchQueuedBuffer>& buffers) {
+    ATRACE_CALL();
+    ALOGV("Surface::queueBuffers");
+    Mutex::Autolock lock(mMutex);
+
+    if (mSharedBufferMode) {
+        ALOGE("%s: batched operation is not supported in shared buffer mode", __FUNCTION__);
+        return INVALID_OPERATION;
+    }
+
+    size_t numBuffers = buffers.size();
+    std::vector<IGraphicBufferProducer::QueueBufferInput> queueBufferInputs(numBuffers);
+    std::vector<IGraphicBufferProducer::QueueBufferOutput> queueBufferOutputs;
+    std::vector<int> bufferSlots(numBuffers, -1);
+    std::vector<sp<Fence>> bufferFences(numBuffers);
+
+    for (size_t batchIdx = 0; batchIdx < numBuffers; batchIdx++) {
+        int i = getSlotFromBufferLocked(buffers[batchIdx].buffer);
+        if (i < 0) {
+            if (buffers[batchIdx].fenceFd >= 0) {
+                close(buffers[batchIdx].fenceFd);
+            }
+            return i;
+        }
+        bufferSlots[batchIdx] = i;
+
+        IGraphicBufferProducer::QueueBufferInput input;
+        getQueueBufferInputLocked(
+                buffers[batchIdx].buffer, buffers[batchIdx].fenceFd, buffers[batchIdx].timestamp,
+                &input);
+        bufferFences[batchIdx] = input.fence;
+        queueBufferInputs[batchIdx] = input;
+    }
+
+    nsecs_t now = systemTime();
+    status_t err = mGraphicBufferProducer->queueBuffers(queueBufferInputs, &queueBufferOutputs);
+    mLastQueueDuration = systemTime() - now;
+    if (err != OK)  {
+        ALOGE("%s: error queuing buffer, %d", __FUNCTION__, err);
+    }
+
+
+    for (size_t batchIdx = 0; batchIdx < numBuffers; batchIdx++) {
+        onBufferQueuedLocked(bufferSlots[batchIdx], bufferFences[batchIdx],
+                queueBufferOutputs[batchIdx]);
+    }
 
     return err;
 }
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 82bc5c9..43b5dcd 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -344,6 +344,23 @@
     static status_t attachAndQueueBufferWithDataspace(Surface* surface, sp<GraphicBuffer> buffer,
                                                       ui::Dataspace dataspace);
 
+    // Batch version of dequeueBuffer, cancelBuffer and queueBuffer
+    // Note that these batched operations are not supported when shared buffer mode is being used.
+    struct BatchBuffer {
+        ANativeWindowBuffer* buffer = nullptr;
+        int fenceFd = -1;
+    };
+    virtual int dequeueBuffers(std::vector<BatchBuffer>* buffers);
+    virtual int cancelBuffers(const std::vector<BatchBuffer>& buffers);
+
+    struct BatchQueuedBuffer {
+        ANativeWindowBuffer* buffer = nullptr;
+        int fenceFd = -1;
+        nsecs_t timestamp = NATIVE_WINDOW_TIMESTAMP_AUTO;
+    };
+    virtual int queueBuffers(
+            const std::vector<BatchQueuedBuffer>& buffers);
+
 protected:
     enum { NUM_BUFFER_SLOTS = BufferQueueDefs::NUM_BUFFER_SLOTS };
     enum { DEFAULT_FORMAT = PIXEL_FORMAT_RGBA_8888 };
@@ -373,6 +390,14 @@
     void freeAllBuffers();
     int getSlotFromBufferLocked(android_native_buffer_t* buffer) const;
 
+    void getDequeueBufferInputLocked(IGraphicBufferProducer::DequeueBufferInput* dequeueInput);
+
+    void getQueueBufferInputLocked(android_native_buffer_t* buffer, int fenceFd, nsecs_t timestamp,
+            IGraphicBufferProducer::QueueBufferInput* out);
+
+    void onBufferQueuedLocked(int slot, sp<Fence> fence,
+            const IGraphicBufferProducer::QueueBufferOutput& output);
+
     struct BufferSlot {
         sp<GraphicBuffer> buffer;
         Region dirtyRegion;
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index fa98cd4c..63db9a7 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -2024,4 +2024,86 @@
     EXPECT_EQ(BufferQueueDefs::NUM_BUFFER_SLOTS, count);
 }
 
+TEST_F(SurfaceTest, BatchOperations) {
+    const int BUFFER_COUNT = 16;
+    const int BATCH_SIZE = 8;
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<CpuConsumer> cpuConsumer = new CpuConsumer(consumer, 1);
+    sp<Surface> surface = new Surface(producer);
+    sp<ANativeWindow> window(surface);
+    sp<StubProducerListener> listener = new StubProducerListener();
+
+    ASSERT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, /*listener*/listener,
+            /*reportBufferRemoval*/false));
+
+    ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), BUFFER_COUNT));
+
+    std::vector<Surface::BatchBuffer> buffers(BATCH_SIZE);
+
+    // Batch dequeued buffers can be queued individually
+    ASSERT_EQ(NO_ERROR, surface->dequeueBuffers(&buffers));
+    for (size_t i = 0; i < BATCH_SIZE; i++) {
+        ANativeWindowBuffer* buffer = buffers[i].buffer;
+        int fence = buffers[i].fenceFd;
+        ASSERT_EQ(NO_ERROR, window->queueBuffer(window.get(), buffer, fence));
+    }
+
+    // Batch dequeued buffers can be canceled individually
+    ASSERT_EQ(NO_ERROR, surface->dequeueBuffers(&buffers));
+    for (size_t i = 0; i < BATCH_SIZE; i++) {
+        ANativeWindowBuffer* buffer = buffers[i].buffer;
+        int fence = buffers[i].fenceFd;
+        ASSERT_EQ(NO_ERROR, window->cancelBuffer(window.get(), buffer, fence));
+    }
+
+    // Batch dequeued buffers can be batch cancelled
+    ASSERT_EQ(NO_ERROR, surface->dequeueBuffers(&buffers));
+    ASSERT_EQ(NO_ERROR, surface->cancelBuffers(buffers));
+
+    // Batch dequeued buffers can be batch queued
+    ASSERT_EQ(NO_ERROR, surface->dequeueBuffers(&buffers));
+    std::vector<Surface::BatchQueuedBuffer> queuedBuffers(BATCH_SIZE);
+    for (size_t i = 0; i < BATCH_SIZE; i++) {
+        queuedBuffers[i].buffer = buffers[i].buffer;
+        queuedBuffers[i].fenceFd = buffers[i].fenceFd;
+        queuedBuffers[i].timestamp = NATIVE_WINDOW_TIMESTAMP_AUTO;
+    }
+    ASSERT_EQ(NO_ERROR, surface->queueBuffers(queuedBuffers));
+
+    ASSERT_EQ(NO_ERROR, surface->disconnect(NATIVE_WINDOW_API_CPU));
+}
+
+TEST_F(SurfaceTest, BatchIllegalOperations) {
+    const int BUFFER_COUNT = 16;
+    const int BATCH_SIZE = 8;
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<CpuConsumer> cpuConsumer = new CpuConsumer(consumer, 1);
+    sp<Surface> surface = new Surface(producer);
+    sp<ANativeWindow> window(surface);
+    sp<StubProducerListener> listener = new StubProducerListener();
+
+    ASSERT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, /*listener*/listener,
+            /*reportBufferRemoval*/false));
+
+    ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), BUFFER_COUNT));
+
+    std::vector<Surface::BatchBuffer> buffers(BATCH_SIZE);
+    std::vector<Surface::BatchQueuedBuffer> queuedBuffers(BATCH_SIZE);
+
+    // Batch operations are invalid in shared buffer mode
+    surface->setSharedBufferMode(true);
+    ASSERT_EQ(INVALID_OPERATION, surface->dequeueBuffers(&buffers));
+    ASSERT_EQ(INVALID_OPERATION, surface->cancelBuffers(buffers));
+    ASSERT_EQ(INVALID_OPERATION, surface->queueBuffers(queuedBuffers));
+    surface->setSharedBufferMode(false);
+
+    ASSERT_EQ(NO_ERROR, surface->disconnect(NATIVE_WINDOW_API_CPU));
+}
+
 } // namespace android
diff --git a/libs/vibrator/Android.bp b/libs/vibrator/Android.bp
index 49bc6bf..f570b83 100644
--- a/libs/vibrator/Android.bp
+++ b/libs/vibrator/Android.bp
@@ -55,3 +55,21 @@
         },
     },
 }
+
+cc_library_headers {
+    name: "libvibrator_headers",
+    vendor_available: true,
+    host_supported: true,
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    static_libs: [
+        "libvibrator",
+    ],
+
+    export_static_lib_headers: [
+        "libvibrator",
+    ],
+}
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 32110e9..9230e72 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -233,8 +233,8 @@
     mFlinger.mTransactionFlags.fetch_or(eTransactionMask);
 }
 
-void RefreshRateOverlay::changeRefreshRate(const RefreshRate& refreshRate) {
-    mCurrentFps = refreshRate.getFps().getIntValue();
+void RefreshRateOverlay::changeRefreshRate(const Fps& fps) {
+    mCurrentFps = fps.getIntValue();
     auto buffer = getOrCreateBuffers(*mCurrentFps)[mFrame];
     mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, true, {},
                       mLayer->getHeadFrameNumber(-1 /* expectedPresentTime */),
@@ -258,8 +258,9 @@
 
 void RefreshRateOverlay::reset() {
     mBufferCache.clear();
-    mLowFps = mFlinger.mRefreshRateConfigs->getMinRefreshRate().getFps().getIntValue();
-    mHighFps = mFlinger.mRefreshRateConfigs->getMaxRefreshRate().getFps().getIntValue();
+    const auto range = mFlinger.mRefreshRateConfigs->getSupportedRefreshRateRange();
+    mLowFps = range.min.getIntValue();
+    mHighFps = range.max.getIntValue();
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index 4ca1337..c16cfa0 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -23,7 +23,7 @@
 #include <ui/Size.h>
 #include <utils/StrongPointer.h>
 
-#include "Scheduler/RefreshRateConfigs.h"
+#include "Fps.h"
 
 namespace android {
 
@@ -34,14 +34,12 @@
 class Layer;
 class SurfaceFlinger;
 
-using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
-
 class RefreshRateOverlay {
 public:
     RefreshRateOverlay(SurfaceFlinger&, bool showSpinner);
 
     void setViewport(ui::Size);
-    void changeRefreshRate(const RefreshRate&);
+    void changeRefreshRate(const Fps&);
     void onInvalidate();
     void reset();
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index c42c812..975754b 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -39,6 +39,26 @@
                               toString(layer.seamlessness).c_str(),
                               to_string(layer.desiredRefreshRate).c_str());
 }
+
+std::vector<Fps> constructKnownFrameRates(
+        const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs) {
+    std::vector<Fps> knownFrameRates = {Fps(24.0f), Fps(30.0f), Fps(45.0f), Fps(60.0f), Fps(72.0f)};
+    knownFrameRates.reserve(knownFrameRates.size() + configs.size());
+
+    // Add all supported refresh rates to the set
+    for (const auto& config : configs) {
+        const auto refreshRate = Fps::fromPeriodNsecs(config->getVsyncPeriod());
+        knownFrameRates.emplace_back(refreshRate);
+    }
+
+    // Sort and remove duplicates
+    std::sort(knownFrameRates.begin(), knownFrameRates.end(), Fps::comparesLess);
+    knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
+                                      Fps::EqualsWithMargin()),
+                          knownFrameRates.end());
+    return knownFrameRates;
+}
+
 } // namespace
 
 using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
@@ -154,9 +174,9 @@
     float score;
 };
 
-const RefreshRate& RefreshRateConfigs::getBestRefreshRate(
-        const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals,
-        GlobalSignals* outSignalsConsidered) const {
+RefreshRate RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequirement>& layers,
+                                                   const GlobalSignals& globalSignals,
+                                                   GlobalSignals* outSignalsConsidered) const {
     ATRACE_CALL();
     ALOGV("getBestRefreshRate %zu layers", layers.size());
 
@@ -469,9 +489,20 @@
     return bestRefreshRate;
 }
 
-const RefreshRate& RefreshRateConfigs::getMinRefreshRateByPolicy() const {
+std::optional<Fps> RefreshRateConfigs::onKernelTimerChanged(
+        std::optional<HwcConfigIndexType> desiredActiveConfigId, bool timerExpired) const {
     std::lock_guard lock(mLock);
-    return getMinRefreshRateByPolicyLocked();
+
+    const auto& current = desiredActiveConfigId ? *mRefreshRates.at(*desiredActiveConfigId)
+                                                : *mCurrentRefreshRate;
+    const auto& min = *mMinSupportedRefreshRate;
+
+    if (current != min) {
+        const auto& refreshRate = timerExpired ? min : current;
+        return refreshRate.getFps();
+    }
+
+    return {};
 }
 
 const RefreshRate& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() const {
@@ -487,7 +518,7 @@
     return *mPrimaryRefreshRates.front();
 }
 
-const RefreshRate& RefreshRateConfigs::getMaxRefreshRateByPolicy() const {
+RefreshRate RefreshRateConfigs::getMaxRefreshRateByPolicy() const {
     std::lock_guard lock(mLock);
     return getMaxRefreshRateByPolicyLocked();
 }
@@ -506,12 +537,12 @@
     return *mPrimaryRefreshRates.back();
 }
 
-const RefreshRate& RefreshRateConfigs::getCurrentRefreshRate() const {
+RefreshRate RefreshRateConfigs::getCurrentRefreshRate() const {
     std::lock_guard lock(mLock);
     return *mCurrentRefreshRate;
 }
 
-const RefreshRate& RefreshRateConfigs::getCurrentRefreshRateByPolicy() const {
+RefreshRate RefreshRateConfigs::getCurrentRefreshRateByPolicy() const {
     std::lock_guard lock(mLock);
     return getCurrentRefreshRateByPolicyLocked();
 }
@@ -533,16 +564,23 @@
         const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
         HwcConfigIndexType currentConfigId)
       : mKnownFrameRates(constructKnownFrameRates(configs)) {
+    updateDisplayConfigs(configs, currentConfigId);
+}
+
+void RefreshRateConfigs::updateDisplayConfigs(
+        const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
+        HwcConfigIndexType currentConfigId) {
+    std::lock_guard lock(mLock);
     LOG_ALWAYS_FATAL_IF(configs.empty());
     LOG_ALWAYS_FATAL_IF(currentConfigId.value() < 0);
     LOG_ALWAYS_FATAL_IF(currentConfigId.value() >= configs.size());
 
+    mRefreshRates.clear();
     for (auto configId = HwcConfigIndexType(0); configId.value() < configs.size(); configId++) {
         const auto& config = configs.at(static_cast<size_t>(configId.value()));
+        const auto fps = Fps::fromPeriodNsecs(config->getVsyncPeriod());
         mRefreshRates.emplace(configId,
-                              std::make_unique<RefreshRate>(configId, config,
-                                                            Fps::fromPeriodNsecs(
-                                                                    config->getVsyncPeriod()),
+                              std::make_unique<RefreshRate>(configId, config, fps,
                                                             RefreshRate::ConstructorTag(0)));
         if (configId == currentConfigId) {
             mCurrentRefreshRate = mRefreshRates.at(configId).get();
@@ -550,7 +588,7 @@
     }
 
     std::vector<const RefreshRate*> sortedConfigs;
-    getSortedRefreshRateList([](const RefreshRate&) { return true; }, &sortedConfigs);
+    getSortedRefreshRateListLocked([](const RefreshRate&) { return true; }, &sortedConfigs);
     mDisplayManagerPolicy.defaultConfig = currentConfigId;
     mMinSupportedRefreshRate = sortedConfigs.front();
     mMaxSupportedRefreshRate = sortedConfigs.back();
@@ -570,7 +608,7 @@
     constructAvailableRefreshRates();
 }
 
-bool RefreshRateConfigs::isPolicyValid(const Policy& policy) {
+bool RefreshRateConfigs::isPolicyValidLocked(const Policy& policy) const {
     // defaultConfig must be a valid config, and within the given refresh rate range.
     auto iter = mRefreshRates.find(policy.defaultConfig);
     if (iter == mRefreshRates.end()) {
@@ -588,7 +626,7 @@
 
 status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) {
     std::lock_guard lock(mLock);
-    if (!isPolicyValid(policy)) {
+    if (!isPolicyValidLocked(policy)) {
         ALOGE("Invalid refresh rate policy: %s", policy.toString().c_str());
         return BAD_VALUE;
     }
@@ -603,7 +641,7 @@
 
 status_t RefreshRateConfigs::setOverridePolicy(const std::optional<Policy>& policy) {
     std::lock_guard lock(mLock);
-    if (policy && !isPolicyValid(*policy)) {
+    if (policy && !isPolicyValidLocked(*policy)) {
         return BAD_VALUE;
     }
     Policy previousPolicy = *getCurrentPolicyLocked();
@@ -639,14 +677,14 @@
     return false;
 }
 
-void RefreshRateConfigs::getSortedRefreshRateList(
+void RefreshRateConfigs::getSortedRefreshRateListLocked(
         const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
         std::vector<const RefreshRate*>* outRefreshRates) {
     outRefreshRates->clear();
     outRefreshRates->reserve(mRefreshRates.size());
     for (const auto& [type, refreshRate] : mRefreshRates) {
         if (shouldAddRefreshRate(*refreshRate)) {
-            ALOGV("getSortedRefreshRateList: config %d added to list policy",
+            ALOGV("getSortedRefreshRateListLocked: config %d added to list policy",
                   refreshRate->configId.value());
             outRefreshRates->push_back(refreshRate.get());
         }
@@ -672,8 +710,9 @@
     ALOGV("constructAvailableRefreshRates: %s ", policy->toString().c_str());
 
     auto filterRefreshRates = [&](Fps min, Fps max, const char* listName,
-                                  std::vector<const RefreshRate*>* outRefreshRates) {
-        getSortedRefreshRateList(
+                                  std::vector<const RefreshRate*>*
+                                          outRefreshRates) REQUIRES(mLock) {
+        getSortedRefreshRateListLocked(
                 [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
                     const auto& hwcConfig = refreshRate.hwcConfig;
 
@@ -706,25 +745,6 @@
                        &mAppRequestRefreshRates);
 }
 
-std::vector<Fps> RefreshRateConfigs::constructKnownFrameRates(
-        const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs) {
-    std::vector<Fps> knownFrameRates = {Fps(24.0f), Fps(30.0f), Fps(45.0f), Fps(60.0f), Fps(72.0f)};
-    knownFrameRates.reserve(knownFrameRates.size() + configs.size());
-
-    // Add all supported refresh rates to the set
-    for (const auto& config : configs) {
-        const auto refreshRate = Fps::fromPeriodNsecs(config->getVsyncPeriod());
-        knownFrameRates.emplace_back(refreshRate);
-    }
-
-    // Sort and remove duplicates
-    std::sort(knownFrameRates.begin(), knownFrameRates.end(), Fps::comparesLess);
-    knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
-                                      Fps::EqualsWithMargin()),
-                          knownFrameRates.end());
-    return knownFrameRates;
-}
-
 Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const {
     if (frameRate.lessThanOrEqualWithMargin(*mKnownFrameRates.begin())) {
         return *mKnownFrameRates.begin();
@@ -744,7 +764,7 @@
 
 RefreshRateConfigs::KernelIdleTimerAction RefreshRateConfigs::getIdleTimerAction() const {
     std::lock_guard lock(mLock);
-    const auto& deviceMin = getMinRefreshRate();
+    const auto& deviceMin = *mMinSupportedRefreshRate;
     const auto& minByPolicy = getMinRefreshRateByPolicyLocked();
     const auto& maxByPolicy = getMaxRefreshRateByPolicyLocked();
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index e4bbf7f..4b99145 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -68,8 +68,6 @@
                     std::shared_ptr<const HWC2::Display::Config> config, Fps fps, ConstructorTag)
               : configId(configId), hwcConfig(config), fps(std::move(fps)) {}
 
-        RefreshRate(const RefreshRate&) = delete;
-
         HwcConfigIndexType getConfigId() const { return configId; }
         nsecs_t getVsyncPeriod() const { return hwcConfig->getVsyncPeriod(); }
         int32_t getConfigGroup() const { return hwcConfig->getConfigGroup(); }
@@ -111,27 +109,26 @@
     using AllRefreshRatesMapType =
             std::unordered_map<HwcConfigIndexType, std::unique_ptr<const RefreshRate>>;
 
+    struct FpsRange {
+        Fps min{0.0f};
+        Fps max{std::numeric_limits<float>::max()};
+
+        bool operator==(const FpsRange& other) const {
+            return min.equalsWithMargin(other.min) && max.equalsWithMargin(other.max);
+        }
+
+        bool operator!=(const FpsRange& other) const { return !(*this == other); }
+
+        std::string toString() const {
+            return base::StringPrintf("[%s %s]", to_string(min).c_str(), to_string(max).c_str());
+        }
+    };
+
     struct Policy {
     private:
         static constexpr int kAllowGroupSwitchingDefault = false;
 
     public:
-        struct Range {
-            Fps min{0.0f};
-            Fps max{std::numeric_limits<float>::max()};
-
-            bool operator==(const Range& other) const {
-                return min.equalsWithMargin(other.min) && max.equalsWithMargin(other.max);
-            }
-
-            bool operator!=(const Range& other) const { return !(*this == other); }
-
-            std::string toString() const {
-                return base::StringPrintf("[%s %s]", to_string(min).c_str(),
-                                          to_string(max).c_str());
-            }
-        };
-
         // The default config, used to ensure we only initiate display config switches within the
         // same config group as defaultConfigId's group.
         HwcConfigIndexType defaultConfig;
@@ -140,29 +137,29 @@
         // The primary refresh rate range represents display manager's general guidance on the
         // display configs we'll consider when switching refresh rates. Unless we get an explicit
         // signal from an app, we should stay within this range.
-        Range primaryRange;
+        FpsRange primaryRange;
         // The app request refresh rate range allows us to consider more display configs when
         // switching refresh rates. Although we should generally stay within the primary range,
         // specific considerations, such as layer frame rate settings specified via the
         // setFrameRate() api, may cause us to go outside the primary range. We never go outside the
         // app request range. The app request range will be greater than or equal to the primary
         // refresh rate range, never smaller.
-        Range appRequestRange;
+        FpsRange appRequestRange;
 
         Policy() = default;
 
-        Policy(HwcConfigIndexType defaultConfig, const Range& range)
+        Policy(HwcConfigIndexType defaultConfig, const FpsRange& range)
               : Policy(defaultConfig, kAllowGroupSwitchingDefault, range, range) {}
 
-        Policy(HwcConfigIndexType defaultConfig, bool allowGroupSwitching, const Range& range)
+        Policy(HwcConfigIndexType defaultConfig, bool allowGroupSwitching, const FpsRange& range)
               : Policy(defaultConfig, allowGroupSwitching, range, range) {}
 
-        Policy(HwcConfigIndexType defaultConfig, const Range& primaryRange,
-               const Range& appRequestRange)
+        Policy(HwcConfigIndexType defaultConfig, const FpsRange& primaryRange,
+               const FpsRange& appRequestRange)
               : Policy(defaultConfig, kAllowGroupSwitchingDefault, primaryRange, appRequestRange) {}
 
         Policy(HwcConfigIndexType defaultConfig, bool allowGroupSwitching,
-               const Range& primaryRange, const Range& appRequestRange)
+               const FpsRange& primaryRange, const FpsRange& appRequestRange)
               : defaultConfig(defaultConfig),
                 allowGroupSwitching(allowGroupSwitching),
                 primaryRange(primaryRange),
@@ -258,35 +255,35 @@
     //   globalSignals - global state of touch and idle
     //   outSignalsConsidered - An output param that tells the caller whether the refresh rate was
     //                          chosen based on touch boost and/or idle timer.
-    const RefreshRate& getBestRefreshRate(const std::vector<LayerRequirement>& layers,
-                                          const GlobalSignals& globalSignals,
-                                          GlobalSignals* outSignalsConsidered = nullptr) const
+    RefreshRate getBestRefreshRate(const std::vector<LayerRequirement>& layers,
+                                   const GlobalSignals& globalSignals,
+                                   GlobalSignals* outSignalsConsidered = nullptr) const
             EXCLUDES(mLock);
 
-    // Returns the lowest refresh rate supported by the device. This won't change at runtime.
-    const RefreshRate& getMinRefreshRate() const { return *mMinSupportedRefreshRate; }
+    FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
+        std::lock_guard lock(mLock);
+        return {mMinSupportedRefreshRate->getFps(), mMaxSupportedRefreshRate->getFps()};
+    }
 
-    // Returns the lowest refresh rate according to the current policy. May change at runtime. Only
-    // uses the primary range, not the app request range.
-    const RefreshRate& getMinRefreshRateByPolicy() const EXCLUDES(mLock);
-
-    // Returns the highest refresh rate supported by the device. This won't change at runtime.
-    const RefreshRate& getMaxRefreshRate() const { return *mMaxSupportedRefreshRate; }
+    std::optional<Fps> onKernelTimerChanged(std::optional<HwcConfigIndexType> desiredActiveConfigId,
+                                            bool timerExpired) const EXCLUDES(mLock);
 
     // Returns the highest refresh rate according to the current policy. May change at runtime. Only
     // uses the primary range, not the app request range.
-    const RefreshRate& getMaxRefreshRateByPolicy() const EXCLUDES(mLock);
+    RefreshRate getMaxRefreshRateByPolicy() const EXCLUDES(mLock);
 
     // Returns the current refresh rate
-    const RefreshRate& getCurrentRefreshRate() const EXCLUDES(mLock);
+    RefreshRate getCurrentRefreshRate() const EXCLUDES(mLock);
 
     // Returns the current refresh rate, if allowed. Otherwise the default that is allowed by
     // the policy.
-    const RefreshRate& getCurrentRefreshRateByPolicy() const;
+    RefreshRate getCurrentRefreshRateByPolicy() const;
 
-    // Returns the refresh rate that corresponds to a HwcConfigIndexType. This won't change at
+    // Returns the refresh rate that corresponds to a HwcConfigIndexType. This may change at
     // runtime.
-    const RefreshRate& getRefreshRateFromConfigId(HwcConfigIndexType configId) const {
+    // TODO(b/159590486) An invalid config id may be given here if the dipslay configs have changed.
+    RefreshRate getRefreshRateFromConfigId(HwcConfigIndexType configId) const EXCLUDES(mLock) {
+        std::lock_guard lock(mLock);
         return *mRefreshRates.at(configId);
     };
 
@@ -302,10 +299,17 @@
     RefreshRateConfigs(const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
                        HwcConfigIndexType currentConfigId);
 
+    void updateDisplayConfigs(
+            const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
+            HwcConfigIndexType currentConfig) EXCLUDES(mLock);
+
     // Returns whether switching configs (refresh rate or resolution) is possible.
     // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the configs only
     // differ in resolution.
-    bool canSwitch() const { return mRefreshRates.size() > 1; }
+    bool canSwitch() const EXCLUDES(mLock) {
+        std::lock_guard lock(mLock);
+        return mRefreshRates.size() > 1;
+    }
 
     // Class to enumerate options around toggling the kernel timer on and off. We have an option
     // for no change to avoid extra calls to kernel.
@@ -334,12 +338,10 @@
     friend class RefreshRateConfigsTest;
 
     void constructAvailableRefreshRates() REQUIRES(mLock);
-    static std::vector<Fps> constructKnownFrameRates(
-            const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs);
 
-    void getSortedRefreshRateList(
+    void getSortedRefreshRateListLocked(
             const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
-            std::vector<const RefreshRate*>* outRefreshRates);
+            std::vector<const RefreshRate*>* outRefreshRates) REQUIRES(mLock);
 
     // Returns the refresh rate with the highest score in the collection specified from begin
     // to end. If there are more than one with the same highest refresh rate, the first one is
@@ -364,7 +366,7 @@
     const RefreshRate& getCurrentRefreshRateByPolicyLocked() const REQUIRES(mLock);
 
     const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
-    bool isPolicyValid(const Policy& policy);
+    bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock);
 
     // Return the display refresh rate divider to match the layer
     // frame rate, or 0 if the display refresh rate is not a multiple of the
@@ -376,9 +378,9 @@
     float calculateLayerScoreLocked(const LayerRequirement&, const RefreshRate&,
                                     bool isSeamlessSwitch) const REQUIRES(mLock);
 
-    // The list of refresh rates, indexed by display config ID. This must not change after this
+    // The list of refresh rates, indexed by display config ID. This may change after this
     // object is initialized.
-    AllRefreshRatesMapType mRefreshRates;
+    AllRefreshRatesMapType mRefreshRates GUARDED_BY(mLock);
 
     // The list of refresh rates in the primary range of the current policy, ordered by vsyncPeriod
     // (the first element is the lowest refresh rate).
@@ -398,9 +400,9 @@
     std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
 
     // The min and max refresh rates supported by the device.
-    // This will not change at runtime.
-    const RefreshRate* mMinSupportedRefreshRate;
-    const RefreshRate* mMaxSupportedRefreshRate;
+    // This may change at runtime.
+    const RefreshRate* mMinSupportedRefreshRate GUARDED_BY(mLock);
+    const RefreshRate* mMaxSupportedRefreshRate GUARDED_BY(mLock);
 
     mutable std::mutex mLock;
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 18c899b..49e3903 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -612,7 +612,7 @@
         mFeatures.contentRequirements = summary;
 
         newConfigId = calculateRefreshRateConfigIndexType(&consideredSignals);
-        auto& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
+        auto newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
         frameRateOverridesChanged =
                 updateFrameRateOverrides(consideredSignals, newRefreshRate.getFps());
 
@@ -629,7 +629,7 @@
         }
     }
     if (frameRateChanged) {
-        auto& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
+        auto newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
         mSchedulerCallback.changeRefreshRate(newRefreshRate,
                                              consideredSignals.idle ? ConfigEvent::None
                                                                     : ConfigEvent::Changed);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 59b3824..2e00ca8 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -995,7 +995,7 @@
 
 void SurfaceFlinger::setDesiredActiveConfig(const ActiveConfigInfo& info) {
     ATRACE_CALL();
-    auto& refreshRate = mRefreshRateConfigs->getRefreshRateFromConfigId(info.configId);
+    auto refreshRate = mRefreshRateConfigs->getRefreshRateFromConfigId(info.configId);
     ALOGV("setDesiredActiveConfig(%s)", refreshRate.getName().c_str());
 
     std::lock_guard<std::mutex> lock(mActiveConfigLock);
@@ -1030,7 +1030,7 @@
     }
 
     if (mRefreshRateOverlay) {
-        mRefreshRateOverlay->changeRefreshRate(refreshRate);
+        mRefreshRateOverlay->changeRefreshRate(refreshRate.getFps());
     }
 }
 
@@ -1076,14 +1076,14 @@
         return;
     }
 
-    auto& oldRefreshRate =
+    auto oldRefreshRate =
             mRefreshRateConfigs->getRefreshRateFromConfigId(display->getActiveConfig());
 
     std::lock_guard<std::mutex> lock(mActiveConfigLock);
     mRefreshRateConfigs->setCurrentConfigId(mUpcomingActiveConfig.configId);
     display->setActiveConfig(mUpcomingActiveConfig.configId);
 
-    auto& refreshRate =
+    auto refreshRate =
             mRefreshRateConfigs->getRefreshRateFromConfigId(mUpcomingActiveConfig.configId);
     mRefreshRateStats->setRefreshRate(refreshRate.getFps());
 
@@ -1126,7 +1126,7 @@
         return;
     }
 
-    auto& refreshRate =
+    auto refreshRate =
             mRefreshRateConfigs->getRefreshRateFromConfigId(desiredActiveConfig->configId);
     ALOGV("performSetActiveConfig changing active config to %d(%s)",
           refreshRate.getConfigId().value(), refreshRate.getName().c_str());
@@ -2620,6 +2620,9 @@
             if (currentState.physical->hwcDisplayId == getHwComposer().getInternalHwcDisplayId()) {
                 const auto displayId = currentState.physical->id;
                 const auto configs = getHwComposer().getConfigs(displayId);
+                const auto currentConfig =
+                        HwcConfigIndexType(getHwComposer().getActiveConfigIndex(displayId));
+                mRefreshRateConfigs->updateDisplayConfigs(configs, currentConfig);
                 mVsyncConfiguration->reset();
                 updatePhaseConfiguration(mRefreshRateConfigs->getCurrentRefreshRate());
                 if (mRefreshRateOverlay) {
@@ -5411,16 +5414,16 @@
     // mRefreshRateConfigs->getCurrentRefreshRate()
     static_cast<void>(schedule([=] {
         const auto desiredActiveConfig = getDesiredActiveConfig();
-        const auto& current = desiredActiveConfig
-                ? mRefreshRateConfigs->getRefreshRateFromConfigId(desiredActiveConfig->configId)
-                : mRefreshRateConfigs->getCurrentRefreshRate();
-        const auto& min = mRefreshRateConfigs->getMinRefreshRate();
+        const std::optional<HwcConfigIndexType> desiredConfigId = desiredActiveConfig
+                ? std::make_optional(desiredActiveConfig->configId)
+                : std::nullopt;
 
-        if (current != min) {
-            const bool timerExpired = mKernelIdleTimerEnabled && expired;
-
+        const bool timerExpired = mKernelIdleTimerEnabled && expired;
+        const auto newRefreshRate =
+                mRefreshRateConfigs->onKernelTimerChanged(desiredConfigId, timerExpired);
+        if (newRefreshRate) {
             if (Mutex::Autolock lock(mStateLock); mRefreshRateOverlay) {
-                mRefreshRateOverlay->changeRefreshRate(timerExpired ? min : current);
+                mRefreshRateOverlay->changeRefreshRate(*newRefreshRate);
             }
             mEventQueue->invalidate();
         }
@@ -6070,7 +6073,7 @@
     toggleKernelIdleTimer();
 
     auto configId = mScheduler->getPreferredConfigId();
-    auto& preferredRefreshRate = configId
+    auto preferredRefreshRate = configId
             ? mRefreshRateConfigs->getRefreshRateFromConfigId(*configId)
             // NOTE: Choose the default config ID, if Scheduler doesn't have one in mind.
             : mRefreshRateConfigs->getRefreshRateFromConfigId(currentPolicy.defaultConfig);
@@ -6376,7 +6379,8 @@
                 mRefreshRateOverlay->setViewport(display->getSize());
             }
 
-            mRefreshRateOverlay->changeRefreshRate(mRefreshRateConfigs->getCurrentRefreshRate());
+            mRefreshRateOverlay->changeRefreshRate(
+                    mRefreshRateConfigs->getCurrentRefreshRate().getFps());
         }
     }));
 }
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 45f0ee6..0813968 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -56,6 +56,21 @@
         return refreshRateConfigs.mKnownFrameRates;
     }
 
+    RefreshRate getMinRefreshRateByPolicy(const RefreshRateConfigs& refreshRateConfigs) {
+        std::lock_guard lock(refreshRateConfigs.mLock);
+        return refreshRateConfigs.getMinRefreshRateByPolicyLocked();
+    }
+
+    RefreshRate getMinSupportedRefreshRate(const RefreshRateConfigs& refreshRateConfigs) {
+        std::lock_guard lock(refreshRateConfigs.mLock);
+        return *refreshRateConfigs.mMinSupportedRefreshRate;
+    }
+
+    RefreshRate getMaxSupportedRefreshRate(const RefreshRateConfigs& refreshRateConfigs) {
+        std::lock_guard lock(refreshRateConfigs.mLock);
+        return *refreshRateConfigs.mMaxSupportedRefreshRate;
+    }
+
     // Test config IDs
     static inline const HwcConfigIndexType HWC_CONFIG_ID_60 = HwcConfigIndexType(0);
     static inline const HwcConfigIndexType HWC_CONFIG_ID_90 = HwcConfigIndexType(1);
@@ -209,13 +224,13 @@
             std::make_unique<RefreshRateConfigs>(m60_90Device,
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
 
-    const auto& minRate = refreshRateConfigs->getMinRefreshRate();
-    const auto& performanceRate = refreshRateConfigs->getMaxRefreshRate();
+    const auto& minRate = getMinSupportedRefreshRate(*refreshRateConfigs);
+    const auto& performanceRate = getMaxSupportedRefreshRate(*refreshRateConfigs);
 
     ASSERT_EQ(mExpected60Config, minRate);
     ASSERT_EQ(mExpected90Config, performanceRate);
 
-    const auto& minRateByPolicy = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto& minRateByPolicy = getMinRefreshRateByPolicy(*refreshRateConfigs);
     const auto& performanceRateByPolicy = refreshRateConfigs->getMaxRefreshRateByPolicy();
     ASSERT_EQ(minRateByPolicy, minRate);
     ASSERT_EQ(performanceRateByPolicy, performanceRate);
@@ -226,9 +241,9 @@
             std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
 
-    const auto& minRate = refreshRateConfigs->getMinRefreshRateByPolicy();
-    const auto& performanceRate = refreshRateConfigs->getMaxRefreshRate();
-    const auto& minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto& minRate = getMinRefreshRateByPolicy(*refreshRateConfigs);
+    const auto& performanceRate = getMaxSupportedRefreshRate(*refreshRateConfigs);
+    const auto& minRate60 = getMinRefreshRateByPolicy(*refreshRateConfigs);
     const auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
 
     ASSERT_EQ(mExpected60Config, minRate);
@@ -239,7 +254,7 @@
               0);
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
 
-    const auto& minRate90 = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto& minRate90 = getMinRefreshRateByPolicy(*refreshRateConfigs);
     const auto& performanceRate90 = refreshRateConfigs->getMaxRefreshRateByPolicy();
 
     ASSERT_EQ(mExpected90DifferentGroupConfig, performanceRate);
@@ -252,9 +267,9 @@
             std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentResolutions,
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
 
-    const auto& minRate = refreshRateConfigs->getMinRefreshRateByPolicy();
-    const auto& performanceRate = refreshRateConfigs->getMaxRefreshRate();
-    const auto& minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto& minRate = getMinRefreshRateByPolicy(*refreshRateConfigs);
+    const auto& performanceRate = getMaxSupportedRefreshRate(*refreshRateConfigs);
+    const auto& minRate60 = getMinRefreshRateByPolicy(*refreshRateConfigs);
     const auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
 
     ASSERT_EQ(mExpected60Config, minRate);
@@ -265,7 +280,7 @@
               0);
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
 
-    const auto& minRate90 = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto& minRate90 = getMinRefreshRateByPolicy(*refreshRateConfigs);
     const auto& performanceRate90 = refreshRateConfigs->getMaxRefreshRateByPolicy();
 
     ASSERT_EQ(mExpected90DifferentResolutionConfig, performanceRate);
@@ -278,8 +293,8 @@
             std::make_unique<RefreshRateConfigs>(m60_90Device,
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
 
-    auto& minRate = refreshRateConfigs->getMinRefreshRateByPolicy();
-    auto& performanceRate = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    auto minRate = getMinRefreshRateByPolicy(*refreshRateConfigs);
+    auto performanceRate = refreshRateConfigs->getMaxRefreshRateByPolicy();
 
     ASSERT_EQ(mExpected60Config, minRate);
     ASSERT_EQ(mExpected90Config, performanceRate);
@@ -287,8 +302,8 @@
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
               0);
 
-    auto& minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
-    auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    auto minRate60 = getMinRefreshRateByPolicy(*refreshRateConfigs);
+    auto performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
     ASSERT_EQ(mExpected60Config, minRate60);
     ASSERT_EQ(mExpected60Config, performanceRate60);
 }
@@ -298,20 +313,20 @@
             std::make_unique<RefreshRateConfigs>(m60_90Device,
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
     {
-        auto& current = refreshRateConfigs->getCurrentRefreshRate();
+        auto current = refreshRateConfigs->getCurrentRefreshRate();
         EXPECT_EQ(current.getConfigId(), HWC_CONFIG_ID_60);
     }
 
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
     {
-        auto& current = refreshRateConfigs->getCurrentRefreshRate();
+        auto current = refreshRateConfigs->getCurrentRefreshRate();
         EXPECT_EQ(current.getConfigId(), HWC_CONFIG_ID_90);
     }
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(90), Fps(90)}}),
               0);
     {
-        auto& current = refreshRateConfigs->getCurrentRefreshRate();
+        auto current = refreshRateConfigs->getCurrentRefreshRate();
         EXPECT_EQ(current.getConfigId(), HWC_CONFIG_ID_90);
     }
 }