Add frame rate flexibility token

Add support for temporarily relaxing frame rate restrictions in surface
flinger. This is used by CTS tests to get a consistent device state
while running frame rate tests.

Bug: 148033900

Test: - On a Pixel 4, I turned the brightness down and covered the
ambient light sensor, causing the display manager to set a frame rate
restriction. I ran the frame rate CTS test without these CLs applied,
and confirmed the test failed because surface flinger couldn't switch
frame rates, as expected. Then I ran the tests with the CLs applied, and
confirmed the tests pass.

- I confirmed that, without adopting shell permission identity, the CTS
test is denied the request to acquire a frame rate flexibility token. So
normal apps won't be able to access this.

Change-Id: I6685edc4bc07c7888b79a9dd72a90f56b74e7604
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 8d79cf8..bd4d62c 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -1145,6 +1145,42 @@
             ALOGE("setFrameRate: failed to transact: %s (%d)", strerror(-err), err);
             return err;
         }
+
+        return reply.readInt32();
+    }
+
+    virtual status_t acquireFrameRateFlexibilityToken(sp<IBinder>* outToken) {
+        if (!outToken) return BAD_VALUE;
+
+        Parcel data, reply;
+        status_t err = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+        if (err != NO_ERROR) {
+            ALOGE("acquireFrameRateFlexibilityToken: failed writing interface token: %s (%d)",
+                  strerror(-err), -err);
+            return err;
+        }
+
+        err = remote()->transact(BnSurfaceComposer::ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN, data,
+                                 &reply);
+        if (err != NO_ERROR) {
+            ALOGE("acquireFrameRateFlexibilityToken: failed to transact: %s (%d)", strerror(-err),
+                  err);
+            return err;
+        }
+
+        err = reply.readInt32();
+        if (err != NO_ERROR) {
+            ALOGE("acquireFrameRateFlexibilityToken: call failed: %s (%d)", strerror(-err), err);
+            return err;
+        }
+
+        err = reply.readStrongBinder(outToken);
+        if (err != NO_ERROR) {
+            ALOGE("acquireFrameRateFlexibilityToken: failed reading binder token: %s (%d)",
+                  strerror(-err), err);
+            return err;
+        }
+
         return NO_ERROR;
     }
 };
@@ -1945,6 +1981,16 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
+        case ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            sp<IBinder> token;
+            status_t result = acquireFrameRateFlexibilityToken(&token);
+            reply->writeInt32(result);
+            if (result == NO_ERROR) {
+                reply->writeStrongBinder(token);
+            }
+            return NO_ERROR;
+        }
         default: {
             return BBinder::onTransact(code, data, reply, flags);
         }
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 09487ea..3cef256 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -508,6 +508,14 @@
      */
     virtual status_t setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
                                   int8_t compatibility) = 0;
+
+    /*
+     * Acquire a frame rate flexibility token from SurfaceFlinger. While this token is acquired,
+     * surface flinger will freely switch between frame rates in any way it sees fit, regardless of
+     * the current restrictions applied by DisplayManager. This is useful to get consistent behavior
+     * for tests. Release the token by releasing the returned IBinder reference.
+     */
+    virtual status_t acquireFrameRateFlexibilityToken(sp<IBinder>* outToken) = 0;
 };
 
 // ----------------------------------------------------------------------------
@@ -566,6 +574,7 @@
         GET_GAME_CONTENT_TYPE_SUPPORT,
         SET_GAME_CONTENT_TYPE,
         SET_FRAME_RATE,
+        ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN,
         // Always append new enum to the end.
     };
 
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 8c0f8f8..ef1fd02 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -859,6 +859,8 @@
         return NO_ERROR;
     }
 
+    status_t acquireFrameRateFlexibilityToken(sp<IBinder>* /*outToken*/) { return NO_ERROR; }
+
 protected:
     IBinder* onAsBinder() override { return nullptr; }
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 02d0b53..14ef733 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -301,7 +301,7 @@
                   mCurrentRefreshRate) != mAvailableRefreshRates.end()) {
         return *mCurrentRefreshRate;
     }
-    return *mRefreshRates.at(mDefaultConfig);
+    return *mRefreshRates.at(getCurrentPolicyLocked()->defaultConfig);
 }
 
 void RefreshRateConfigs::setCurrentConfigId(HwcConfigIndexType configId) {
@@ -326,38 +326,59 @@
     init(inputConfigs, currentConfigId);
 }
 
-status_t RefreshRateConfigs::setPolicy(HwcConfigIndexType defaultConfigId, float minRefreshRate,
-                                       float maxRefreshRate, bool* outPolicyChanged) {
+bool RefreshRateConfigs::isPolicyValid(const Policy& policy) {
+    // defaultConfig must be a valid config, and within the given refresh rate range.
+    auto iter = mRefreshRates.find(policy.defaultConfig);
+    if (iter == mRefreshRates.end()) {
+        return false;
+    }
+    const RefreshRate& refreshRate = *iter->second;
+    if (!refreshRate.inPolicy(policy.minRefreshRate, policy.maxRefreshRate)) {
+        return false;
+    }
+    return true;
+}
+
+status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) {
     std::lock_guard lock(mLock);
-    bool policyChanged = defaultConfigId != mDefaultConfig ||
-            minRefreshRate != mMinRefreshRateFps || maxRefreshRate != mMaxRefreshRateFps;
-    if (outPolicyChanged) {
-        *outPolicyChanged = policyChanged;
-    }
-    if (!policyChanged) {
-        return NO_ERROR;
-    }
-    // defaultConfigId must be a valid config ID, and within the given refresh rate range.
-    if (mRefreshRates.count(defaultConfigId) == 0) {
+    if (!isPolicyValid(policy)) {
         return BAD_VALUE;
     }
-    const RefreshRate& refreshRate = *mRefreshRates.at(defaultConfigId);
-    if (!refreshRate.inPolicy(minRefreshRate, maxRefreshRate)) {
-        return BAD_VALUE;
+    Policy previousPolicy = *getCurrentPolicyLocked();
+    mDisplayManagerPolicy = policy;
+    if (*getCurrentPolicyLocked() == previousPolicy) {
+        return CURRENT_POLICY_UNCHANGED;
     }
-    mDefaultConfig = defaultConfigId;
-    mMinRefreshRateFps = minRefreshRate;
-    mMaxRefreshRateFps = maxRefreshRate;
     constructAvailableRefreshRates();
     return NO_ERROR;
 }
 
-void RefreshRateConfigs::getPolicy(HwcConfigIndexType* defaultConfigId, float* minRefreshRate,
-                                   float* maxRefreshRate) const {
+status_t RefreshRateConfigs::setOverridePolicy(const std::optional<Policy>& policy) {
     std::lock_guard lock(mLock);
-    *defaultConfigId = mDefaultConfig;
-    *minRefreshRate = mMinRefreshRateFps;
-    *maxRefreshRate = mMaxRefreshRateFps;
+    if (policy && !isPolicyValid(*policy)) {
+        return BAD_VALUE;
+    }
+    Policy previousPolicy = *getCurrentPolicyLocked();
+    mOverridePolicy = policy;
+    if (*getCurrentPolicyLocked() == previousPolicy) {
+        return CURRENT_POLICY_UNCHANGED;
+    }
+    constructAvailableRefreshRates();
+    return NO_ERROR;
+}
+
+const RefreshRateConfigs::Policy* RefreshRateConfigs::getCurrentPolicyLocked() const {
+    return mOverridePolicy ? &mOverridePolicy.value() : &mDisplayManagerPolicy;
+}
+
+RefreshRateConfigs::Policy RefreshRateConfigs::getCurrentPolicy() const {
+    std::lock_guard lock(mLock);
+    return *getCurrentPolicyLocked();
+}
+
+RefreshRateConfigs::Policy RefreshRateConfigs::getDisplayManagerPolicy() const {
+    std::lock_guard lock(mLock);
+    return mDisplayManagerPolicy;
 }
 
 bool RefreshRateConfigs::isConfigAllowed(HwcConfigIndexType config) const {
@@ -385,19 +406,25 @@
 
     std::sort(outRefreshRates->begin(), outRefreshRates->end(),
               [](const auto refreshRate1, const auto refreshRate2) {
-                  return refreshRate1->vsyncPeriod > refreshRate2->vsyncPeriod;
+                  if (refreshRate1->vsyncPeriod != refreshRate2->vsyncPeriod) {
+                      return refreshRate1->vsyncPeriod > refreshRate2->vsyncPeriod;
+                  } else {
+                      return refreshRate1->configGroup > refreshRate2->configGroup;
+                  }
               });
 }
 
 void RefreshRateConfigs::constructAvailableRefreshRates() {
     // Filter configs based on current policy and sort based on vsync period
-    HwcConfigGroupType group = mRefreshRates.at(mDefaultConfig)->configGroup;
+    const Policy* policy = getCurrentPolicyLocked();
+    HwcConfigGroupType group = mRefreshRates.at(policy->defaultConfig)->configGroup;
     ALOGV("constructAvailableRefreshRates: default %d group %d min %.2f max %.2f",
-          mDefaultConfig.value(), group.value(), mMinRefreshRateFps, mMaxRefreshRateFps);
+          policy->defaultConfig.value(), group.value(), policy->minRefreshRate,
+          policy->maxRefreshRate);
     getSortedRefreshRateList(
             [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
-                return refreshRate.configGroup == group &&
-                        refreshRate.inPolicy(mMinRefreshRateFps, mMaxRefreshRateFps);
+                return (policy->allowGroupSwitching || refreshRate.configGroup == group) &&
+                        refreshRate.inPolicy(policy->minRefreshRate, policy->maxRefreshRate);
             },
             &mAvailableRefreshRates);
 
@@ -409,7 +436,8 @@
     ALOGV("Available refresh rates: %s", availableRefreshRates.c_str());
     LOG_ALWAYS_FATAL_IF(mAvailableRefreshRates.empty(),
                         "No compatible display configs for default=%d min=%.0f max=%.0f",
-                        mDefaultConfig.value(), mMinRefreshRateFps, mMaxRefreshRateFps);
+                        policy->defaultConfig.value(), policy->minRefreshRate,
+                        policy->maxRefreshRate);
 }
 
 // NO_THREAD_SAFETY_ANALYSIS since this is called from the constructor
@@ -432,7 +460,7 @@
 
     std::vector<const RefreshRate*> sortedConfigs;
     getSortedRefreshRateList([](const RefreshRate&) { return true; }, &sortedConfigs);
-    mDefaultConfig = currentHwcConfig;
+    mDisplayManagerPolicy.defaultConfig = currentHwcConfig;
     mMinSupportedRefreshRate = sortedConfigs.front();
     mMaxSupportedRefreshRate = sortedConfigs.back();
     constructAvailableRefreshRates();
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 87d4389..e749f8f 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -20,6 +20,7 @@
 
 #include <algorithm>
 #include <numeric>
+#include <optional>
 #include <type_traits>
 
 #include "DisplayHardware/HWComposer.h"
@@ -90,14 +91,47 @@
     using AllRefreshRatesMapType =
             std::unordered_map<HwcConfigIndexType, std::unique_ptr<const RefreshRate>>;
 
-    // Sets the current policy to choose refresh rates. Returns NO_ERROR if the requested policy is
-    // valid, or a negative error value otherwise. policyChanged, if non-null, will be set to true
-    // if the new policy is different from the old policy.
-    status_t setPolicy(HwcConfigIndexType defaultConfigId, float minRefreshRate,
-                       float maxRefreshRate, bool* policyChanged) EXCLUDES(mLock);
-    // Gets the current policy.
-    void getPolicy(HwcConfigIndexType* defaultConfigId, float* minRefreshRate,
-                   float* maxRefreshRate) const EXCLUDES(mLock);
+    struct Policy {
+        // The default config, used to ensure we only initiate display config switches within the
+        // same config group as defaultConfigId's group.
+        HwcConfigIndexType defaultConfig;
+        // The min and max FPS allowed by the policy.
+        float minRefreshRate = 0;
+        float maxRefreshRate = std::numeric_limits<float>::max();
+        // Whether or not we switch config groups to get the best frame rate. Only used by tests.
+        bool allowGroupSwitching = false;
+
+        bool operator==(const Policy& other) const {
+            return defaultConfig == other.defaultConfig && minRefreshRate == other.minRefreshRate &&
+                    maxRefreshRate == other.maxRefreshRate &&
+                    allowGroupSwitching == other.allowGroupSwitching;
+        }
+
+        bool operator!=(const Policy& other) const { return !(*this == other); }
+    };
+
+    // Return code set*Policy() to indicate the current policy is unchanged.
+    static constexpr int CURRENT_POLICY_UNCHANGED = 1;
+
+    // We maintain the display manager policy and the override policy separately. The override
+    // policy is used by CTS tests to get a consistent device state for testing. While the override
+    // policy is set, it takes precedence over the display manager policy. Once the override policy
+    // is cleared, we revert to using the display manager policy.
+
+    // Sets the display manager policy to choose refresh rates. The return value will be:
+    //   - A negative value if the policy is invalid or another error occurred.
+    //   - NO_ERROR if the policy was successfully updated, and the current policy is different from
+    //     what it was before the call.
+    //   - CURRENT_POLICY_UNCHANGED if the policy was successfully updated, but the current policy
+    //     is the same as it was before the call.
+    status_t setDisplayManagerPolicy(const Policy& policy) EXCLUDES(mLock);
+    // Sets the override policy. See setDisplayManagerPolicy() for the meaning of the return value.
+    status_t setOverridePolicy(const std::optional<Policy>& policy) EXCLUDES(mLock);
+    // Gets the current policy, which will be the override policy if active, and the display manager
+    // policy otherwise.
+    Policy getCurrentPolicy() const EXCLUDES(mLock);
+    // Gets the display manager policy, regardless of whether an override policy is active.
+    Policy getDisplayManagerPolicy() const EXCLUDES(mLock);
 
     // Returns true if config is allowed by the current policy.
     bool isConfigAllowed(HwcConfigIndexType config) const EXCLUDES(mLock);
@@ -208,6 +242,9 @@
     // the policy.
     const RefreshRate& getCurrentRefreshRateByPolicyLocked() const REQUIRES(mLock);
 
+    const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
+    bool isPolicyValid(const Policy& policy);
+
     // The list of refresh rates, indexed by display config ID. This must not change after this
     // object is initialized.
     AllRefreshRatesMapType mRefreshRates;
@@ -220,14 +257,10 @@
     // the main thread, and read by the Scheduler (and other objects) on other threads.
     const RefreshRate* mCurrentRefreshRate GUARDED_BY(mLock);
 
-    // The default config. This will change at runtime. This is set by SurfaceFlinger on
-    // the main thread, and read by the Scheduler (and other objects) on other threads.
-    HwcConfigIndexType mDefaultConfig GUARDED_BY(mLock);
-
-    // The min and max FPS allowed by the policy. This will change at runtime and set by
-    // SurfaceFlinger on the main thread.
-    float mMinRefreshRateFps GUARDED_BY(mLock) = 0;
-    float mMaxRefreshRateFps GUARDED_BY(mLock) = std::numeric_limits<float>::max();
+    // The policy values will change at runtime. They're set by SurfaceFlinger on the main thread,
+    // and read by the Scheduler (and other objects) on other threads.
+    Policy mDisplayManagerPolicy GUARDED_BY(mLock);
+    std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
 
     // The min and max refresh rates supported by the device.
     // This will not change at runtime.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index dd2be7c..c47bf94 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -200,6 +200,15 @@
     return dataspace == Dataspace::V0_SRGB || dataspace == Dataspace::DISPLAY_P3;
 }
 
+class FrameRateFlexibilityToken : public BBinder {
+public:
+    FrameRateFlexibilityToken(std::function<void()> callback) : mCallback(callback) {}
+    virtual ~FrameRateFlexibilityToken() { mCallback(); }
+
+private:
+    std::function<void()> mCallback;
+};
+
 }  // namespace anonymous
 
 // ---------------------------------------------------------------------------
@@ -977,8 +986,11 @@
         } else {
             HwcConfigIndexType config(mode);
             const auto& refreshRate = mRefreshRateConfigs->getRefreshRateFromConfigId(config);
-            result = setDesiredDisplayConfigSpecsInternal(display, config, refreshRate.fps,
-                                                          refreshRate.fps);
+            result = setDesiredDisplayConfigSpecsInternal(display,
+                                                          scheduler::RefreshRateConfigs::
+                                                                  Policy{config, refreshRate.fps,
+                                                                         refreshRate.fps},
+                                                          /*overridePolicy=*/false);
         }
     }));
 
@@ -3496,12 +3508,13 @@
     return flags;
 }
 
-bool SurfaceFlinger::callingThreadHasUnscopedSurfaceFlingerAccess() {
+bool SurfaceFlinger::callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermissionCache) {
     IPCThreadState* ipc = IPCThreadState::self();
     const int pid = ipc->getCallingPid();
     const int uid = ipc->getCallingUid();
     if ((uid != AID_GRAPHICS && uid != AID_SYSTEM) &&
-        !PermissionCache::checkPermission(sAccessSurfaceFlinger, pid, uid)) {
+        (usePermissionCache ? !PermissionCache::checkPermission(sAccessSurfaceFlinger, pid, uid)
+                            : !checkPermission(sAccessSurfaceFlinger, pid, uid))) {
         return false;
     }
     return true;
@@ -4373,13 +4386,11 @@
                   "      present offset: %9" PRId64 " ns\t     VSYNC period: %9" PRId64 " ns\n\n",
                   dispSyncPresentTimeOffset, getVsyncPeriod());
 
-    HwcConfigIndexType defaultConfig;
-    float minFps, maxFps;
-    mRefreshRateConfigs->getPolicy(&defaultConfig, &minFps, &maxFps);
+    scheduler::RefreshRateConfigs::Policy policy = mRefreshRateConfigs->getDisplayManagerPolicy();
     StringAppendF(&result,
                   "DesiredDisplayConfigSpecs: default config ID: %d"
                   ", min: %.2f Hz, max: %.2f Hz",
-                  defaultConfig.value(), minFps, maxFps);
+                  policy.defaultConfig.value(), policy.minRefreshRate, policy.maxRefreshRate);
     StringAppendF(&result, "(config override by backdoor: %s)\n\n",
                   mDebugDisplayConfigSetByBackdoor ? "yes" : "no");
 
@@ -4818,8 +4829,12 @@
         case SET_DISPLAY_CONTENT_SAMPLING_ENABLED:
         case GET_DISPLAYED_CONTENT_SAMPLE:
         case NOTIFY_POWER_HINT:
-        case SET_GLOBAL_SHADOW_SETTINGS: {
-            if (!callingThreadHasUnscopedSurfaceFlingerAccess()) {
+        case SET_GLOBAL_SHADOW_SETTINGS:
+        case ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN: {
+            // ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN is used by CTS tests, which acquire the
+            // necessary permission dynamically. Don't use the permission cache for this check.
+            bool usePermissionCache = code != ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN;
+            if (!callingThreadHasUnscopedSurfaceFlingerAccess(usePermissionCache)) {
                 IPCThreadState* ipc = IPCThreadState::self();
                 ALOGE("Permission Denial: can't access SurfaceFlinger pid=%d, uid=%d",
                         ipc->getCallingPid(), ipc->getCallingUid());
@@ -5908,12 +5923,15 @@
     }
 }
 
-status_t SurfaceFlinger::setDesiredDisplayConfigSpecsInternal(const sp<DisplayDevice>& display,
-                                                              HwcConfigIndexType defaultConfig,
-                                                              float minRefreshRate,
-                                                              float maxRefreshRate) {
+status_t SurfaceFlinger::setDesiredDisplayConfigSpecsInternal(
+        const sp<DisplayDevice>& display,
+        const std::optional<scheduler::RefreshRateConfigs::Policy>& policy, bool overridePolicy) {
     Mutex::Autolock lock(mStateLock);
 
+    LOG_ALWAYS_FATAL_IF(!display->isPrimary() && overridePolicy,
+                        "Can only set override policy on the primary display");
+    LOG_ALWAYS_FATAL_IF(!policy && !overridePolicy, "Can only clear the override policy");
+
     if (!display->isPrimary()) {
         // TODO(b/144711714): For non-primary displays we should be able to set an active config
         // as well. For now, just call directly to setActiveConfigWithConstraints but ideally
@@ -5927,7 +5945,8 @@
         constraints.seamlessRequired = false;
 
         HWC2::VsyncPeriodChangeTimeline timeline = {0, 0, 0};
-        if (getHwComposer().setActiveConfigWithConstraints(*displayId, defaultConfig.value(),
+        if (getHwComposer().setActiveConfigWithConstraints(*displayId,
+                                                           policy->defaultConfig.value(),
                                                            constraints, &timeline) < 0) {
             return BAD_VALUE;
         }
@@ -5935,11 +5954,12 @@
             repaintEverythingForHWC();
         }
 
-        display->setActiveConfig(defaultConfig);
-        const nsecs_t vsyncPeriod =
-                getHwComposer().getConfigs(*displayId)[defaultConfig.value()]->getVsyncPeriod();
-        mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value, defaultConfig,
-                                    vsyncPeriod);
+        display->setActiveConfig(policy->defaultConfig);
+        const nsecs_t vsyncPeriod = getHwComposer()
+                                            .getConfigs(*displayId)[policy->defaultConfig.value()]
+                                            ->getVsyncPeriod();
+        mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value,
+                                    policy->defaultConfig, vsyncPeriod);
         return NO_ERROR;
     }
 
@@ -5948,17 +5968,20 @@
         return NO_ERROR;
     }
 
-    bool policyChanged;
-    if (mRefreshRateConfigs->setPolicy(defaultConfig, minRefreshRate, maxRefreshRate,
-                                       &policyChanged) < 0) {
+    status_t setPolicyResult = overridePolicy
+            ? mRefreshRateConfigs->setOverridePolicy(policy)
+            : mRefreshRateConfigs->setDisplayManagerPolicy(*policy);
+    if (setPolicyResult < 0) {
         return BAD_VALUE;
     }
-    if (!policyChanged) {
+    if (setPolicyResult == scheduler::RefreshRateConfigs::CURRENT_POLICY_UNCHANGED) {
         return NO_ERROR;
     }
+    scheduler::RefreshRateConfigs::Policy currentPolicy = mRefreshRateConfigs->getCurrentPolicy();
 
     ALOGV("Setting desired display config specs: defaultConfig: %d min: %.f max: %.f",
-          defaultConfig.value(), minRefreshRate, maxRefreshRate);
+          currentPolicy.defaultConfig.value(), currentPolicy.minRefreshRate,
+          currentPolicy.maxRefreshRate);
 
     // TODO(b/140204874): This hack triggers a notification that something has changed, so
     // that listeners that care about a change in allowed configs can get the notification.
@@ -5972,7 +5995,7 @@
     auto& preferredRefreshRate = configId
             ? mRefreshRateConfigs->getRefreshRateFromConfigId(*configId)
             // NOTE: Choose the default config ID, if Scheduler doesn't have one in mind.
-            : mRefreshRateConfigs->getRefreshRateFromConfigId(defaultConfig);
+            : mRefreshRateConfigs->getRefreshRateFromConfigId(currentPolicy.defaultConfig);
     ALOGV("trying to switch to Scheduler preferred config %d (%s)",
           preferredRefreshRate.configId.value(), preferredRefreshRate.name.c_str());
 
@@ -6007,9 +6030,13 @@
             result = BAD_VALUE;
             ALOGW("Attempt to set desired display configs for virtual display");
         } else {
-            result =
-                    setDesiredDisplayConfigSpecsInternal(display, HwcConfigIndexType(defaultConfig),
-                                                         minRefreshRate, maxRefreshRate);
+            result = setDesiredDisplayConfigSpecsInternal(display,
+                                                          scheduler::RefreshRateConfigs::
+                                                                  Policy{HwcConfigIndexType(
+                                                                                 defaultConfig),
+                                                                         minRefreshRate,
+                                                                         maxRefreshRate},
+                                                          /*overridePolicy=*/false);
         }
     }));
 
@@ -6033,9 +6060,11 @@
     }
 
     if (display->isPrimary()) {
-        HwcConfigIndexType defaultConfig;
-        mRefreshRateConfigs->getPolicy(&defaultConfig, outMinRefreshRate, outMaxRefreshRate);
-        *outDefaultConfig = defaultConfig.value();
+        scheduler::RefreshRateConfigs::Policy policy =
+                mRefreshRateConfigs->getDisplayManagerPolicy();
+        *outDefaultConfig = policy.defaultConfig.value();
+        *outMinRefreshRate = policy.minRefreshRate;
+        *outMaxRefreshRate = policy.maxRefreshRate;
         return NO_ERROR;
     } else if (display->isVirtual()) {
         return BAD_VALUE;
@@ -6155,6 +6184,68 @@
     return NO_ERROR;
 }
 
+status_t SurfaceFlinger::acquireFrameRateFlexibilityToken(sp<IBinder>* outToken) {
+    if (!outToken) {
+        return BAD_VALUE;
+    }
+    status_t result = NO_ERROR;
+    postMessageSync(new LambdaMessage([&]() {
+        if (mFrameRateFlexibilityTokenCount == 0) {
+            // |mStateLock| not needed as we are on the main thread
+            const auto display = getDefaultDisplayDeviceLocked();
+
+            // This is a little racy, but not in a way that hurts anything. As we grab the
+            // defaultConfig from the display manager policy, we could be setting a new display
+            // manager policy, leaving us using a stale defaultConfig. The defaultConfig doesn't
+            // matter for the override policy though, since we set allowGroupSwitching to true, so
+            // it's not a problem.
+            scheduler::RefreshRateConfigs::Policy overridePolicy;
+            overridePolicy.defaultConfig =
+                    mRefreshRateConfigs->getDisplayManagerPolicy().defaultConfig;
+            overridePolicy.allowGroupSwitching = true;
+            result = setDesiredDisplayConfigSpecsInternal(display, overridePolicy,
+                                                          /*overridePolicy=*/true);
+        }
+
+        if (result == NO_ERROR) {
+            mFrameRateFlexibilityTokenCount++;
+            // Handing out a reference to the SurfaceFlinger object, as we're doing in the line
+            // below, is something to consider carefully. The lifetime of the
+            // FrameRateFlexibilityToken isn't tied to SurfaceFlinger object lifetime, so if this
+            // SurfaceFlinger object were to be destroyed while the token still exists, the token
+            // destructor would be accessing a stale SurfaceFlinger reference, and crash. This is ok
+            // in this case, for two reasons:
+            //   1. Once SurfaceFlinger::run() is called by main_surfaceflinger.cpp, the only way
+            //   the program exits is via a crash. So we won't have a situation where the
+            //   SurfaceFlinger object is dead but the process is still up.
+            //   2. The frame rate flexibility token is acquired/released only by CTS tests, so even
+            //   if condition 1 were changed, the problem would only show up when running CTS tests,
+            //   not on end user devices, so we could spot it and fix it without serious impact.
+            *outToken = new FrameRateFlexibilityToken(
+                    [this]() { onFrameRateFlexibilityTokenReleased(); });
+            ALOGD("Frame rate flexibility token acquired. count=%d",
+                  mFrameRateFlexibilityTokenCount);
+        }
+    }));
+    return result;
+}
+
+void SurfaceFlinger::onFrameRateFlexibilityTokenReleased() {
+    postMessageAsync(new LambdaMessage([&]() {
+        LOG_ALWAYS_FATAL_IF(mFrameRateFlexibilityTokenCount == 0,
+                            "Failed tracking frame rate flexibility tokens");
+        mFrameRateFlexibilityTokenCount--;
+        ALOGD("Frame rate flexibility token released. count=%d", mFrameRateFlexibilityTokenCount);
+        if (mFrameRateFlexibilityTokenCount == 0) {
+            // |mStateLock| not needed as we are on the main thread
+            const auto display = getDefaultDisplayDeviceLocked();
+            status_t result =
+                    setDesiredDisplayConfigSpecsInternal(display, {}, /*overridePolicy=*/true);
+            LOG_ALWAYS_FATAL_IF(result < 0, "Failed releasing frame rate flexibility token");
+        }
+    }));
+}
+
 } // namespace android
 
 #if defined(__gl_h_)
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 60904f6..3997633 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -407,7 +407,8 @@
      */
     status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override;
     status_t dump(int fd, const Vector<String16>& args) override { return priorityDump(fd, args); }
-    bool callingThreadHasUnscopedSurfaceFlingerAccess() EXCLUDES(mStateLock);
+    bool callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermissionCache = true)
+            EXCLUDES(mStateLock);
 
     /* ------------------------------------------------------------------------
      * ISurfaceComposer interface
@@ -503,6 +504,7 @@
                                      float lightPosY, float lightPosZ, float lightRadius) override;
     status_t setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
                           int8_t compatibility) override;
+    status_t acquireFrameRateFlexibilityToken(sp<IBinder>* outToken) override;
     /* ------------------------------------------------------------------------
      * DeathRecipient interface
      */
@@ -574,9 +576,9 @@
     void setPowerModeInternal(const sp<DisplayDevice>& display, int mode) REQUIRES(mStateLock);
 
     // Sets the desired display configs.
-    status_t setDesiredDisplayConfigSpecsInternal(const sp<DisplayDevice>& display,
-                                                  HwcConfigIndexType defaultConfig,
-                                                  float minRefreshRate, float maxRefreshRate)
+    status_t setDesiredDisplayConfigSpecsInternal(
+            const sp<DisplayDevice>& display,
+            const std::optional<scheduler::RefreshRateConfigs::Policy>& policy, bool overridePolicy)
             EXCLUDES(mStateLock);
 
     // called on the main thread in response to setAutoLowLatencyMode()
@@ -968,6 +970,8 @@
         return doDump(fd, args, asProto);
     }
 
+    void onFrameRateFlexibilityTokenReleased();
+
     /* ------------------------------------------------------------------------
      * VrFlinger
      */
@@ -1275,6 +1279,8 @@
     std::atomic<bool> mInputDirty = true;
     void dirtyInput() { mInputDirty = true; }
     bool inputDirty() { return mInputDirty; }
+
+    int mFrameRateFlexibilityTokenCount = 0;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index dd04076..ce41291 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -83,8 +83,8 @@
             {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60}}};
     auto refreshRateConfigs =
             std::make_unique<RefreshRateConfigs>(configs, /*currentConfigId=*/HWC_CONFIG_ID_60);
-    ASSERT_LT(refreshRateConfigs->setPolicy(HwcConfigIndexType(10), 60, 60, nullptr), 0);
-    ASSERT_LT(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 20, 40, nullptr), 0);
+    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HwcConfigIndexType(10), 60, 60}), 0);
+    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, 20, 40}), 0);
 }
 
 TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap) {
@@ -126,7 +126,7 @@
     ASSERT_EQ(expectedDefaultConfig, minRate60);
     ASSERT_EQ(expectedDefaultConfig, performanceRate60);
 
-    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_90, 60, 90, nullptr), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, 60, 90}), 0);
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
 
     const auto& minRate90 = refreshRateConfigs->getMinRefreshRateByPolicy();
@@ -155,7 +155,7 @@
                                              90};
     ASSERT_EQ(expectedPerformanceConfig, performanceRate);
 
-    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 60, 60, nullptr), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, 60, 60}), 0);
 
     auto& minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
     auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
@@ -180,7 +180,7 @@
         EXPECT_EQ(current.configId, HWC_CONFIG_ID_90);
     }
 
-    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_90, 90, 90, nullptr), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, 90, 90}), 0);
     {
         auto& current = refreshRateConfigs->getCurrentRefreshRate();
         EXPECT_EQ(current.configId, HWC_CONFIG_ID_90);
@@ -212,7 +212,7 @@
     EXPECT_EQ(expected60Config,
               refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
 
-    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 60, 60, nullptr), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, 60, 60}), 0);
     EXPECT_EQ(expected60Config,
               refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
     EXPECT_EQ(expected60Config,
@@ -224,7 +224,7 @@
     EXPECT_EQ(expected60Config,
               refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
 
-    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_90, 90, 90, nullptr), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, 90, 90}), 0);
     EXPECT_EQ(expected90Config,
               refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
     EXPECT_EQ(expected90Config,
@@ -235,7 +235,7 @@
               refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
     EXPECT_EQ(expected90Config,
               refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
-    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 0, 120, nullptr), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, 0, 120}), 0);
     EXPECT_EQ(expected90Config,
               refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
     EXPECT_EQ(expected60Config,
@@ -332,7 +332,7 @@
                                                              &ignored));
 
     lr.name = "";
-    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 60, 60, nullptr), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, 60, 60}), 0);
 
     lr.vote = LayerVoteType::Min;
     EXPECT_EQ(expected60Config,
@@ -370,7 +370,7 @@
               refreshRateConfigs->getRefreshRateForContentV2(layers, /*touchActive*/ false,
                                                              &ignored));
 
-    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_90, 90, 90, nullptr), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, 90, 90}), 0);
 
     lr.vote = LayerVoteType::Min;
     EXPECT_EQ(expected90Config,
@@ -408,7 +408,7 @@
               refreshRateConfigs->getRefreshRateForContentV2(layers, /*touchActive*/ false,
                                                              &ignored));
 
-    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 0, 120, nullptr), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, 0, 120}), 0);
     lr.vote = LayerVoteType::Min;
     EXPECT_EQ(expected60Config,
               refreshRateConfigs->getRefreshRateForContentV2(layers, /*touchActive*/ false,
@@ -1218,6 +1218,33 @@
     }
 }
 
+TEST_F(RefreshRateConfigsTest, groupSwitching) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_1, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(configs, /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::ExplicitDefault;
+    layer.desiredRefreshRate = 90.0f;
+    layer.name = "90Hz ExplicitDefault";
+
+    bool touchConsidered;
+    ASSERT_EQ(HWC_CONFIG_ID_60,
+              refreshRateConfigs->getRefreshRateForContentV2(layers, false, &touchConsidered)
+                      .configId);
+
+    RefreshRateConfigs::Policy policy;
+    policy.defaultConfig = refreshRateConfigs->getCurrentPolicy().defaultConfig;
+    policy.allowGroupSwitching = true;
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    ASSERT_EQ(HWC_CONFIG_ID_90,
+              refreshRateConfigs->getRefreshRateForContentV2(layers, false, &touchConsidered)
+                      .configId);
+}
+
 } // namespace
 } // namespace scheduler
 } // namespace android