Merge "BlastBufferQueue: Fix async worker deadlock" into sc-v2-dev
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 5b9d912..e8e664b 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -456,10 +456,10 @@
     // Ensure BLASTBufferQueue stays alive until we receive the transaction complete callback.
     incStrong((void*)transactionCallbackThunk);
 
+    const bool sizeHasChanged = mRequestedSize != mSize;
+    mSize = mRequestedSize;
+    const bool updateDestinationFrame = sizeHasChanged || !mLastBufferInfo.hasBuffer;
     Rect crop = computeCrop(bufferItem);
-    const bool updateDestinationFrame =
-            bufferItem.mScalingMode == NATIVE_WINDOW_SCALING_MODE_FREEZE ||
-            !mLastBufferInfo.hasBuffer;
     mLastBufferInfo.update(true /* hasBuffer */, bufferItem.mGraphicBuffer->getWidth(),
                            bufferItem.mGraphicBuffer->getHeight(), bufferItem.mTransform,
                            bufferItem.mScalingMode, crop);
@@ -586,7 +586,6 @@
 
 bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) {
     if (item.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) {
-        mSize = mRequestedSize;
         // Only reject buffers if scaling mode is freeze.
         return false;
     }
@@ -600,7 +599,6 @@
     }
     ui::Size bufferSize(bufWidth, bufHeight);
     if (mRequestedSize != mSize && mRequestedSize == bufferSize) {
-        mSize = mRequestedSize;
         return false;
     }
 
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index 9082d27..99777e9 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -672,6 +672,83 @@
                                                /*border*/ 0, /*outsideRegion*/ true));
 }
 
+// b/196339769 verify we can can update the requested size while the in FREEZE scaling mode and
+// scale the buffer properly when the mode changes to SCALE_TO_WINDOW
+TEST_F(BLASTBufferQueueTest, ScalingModeChanges) {
+    uint8_t r = 255;
+    uint8_t g = 0;
+    uint8_t b = 0;
+
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight / 4);
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+    {
+        int slot;
+        sp<Fence> fence;
+        sp<GraphicBuffer> buf;
+        auto ret = igbProducer->dequeueBuffer(&slot, &fence, mDisplayWidth, mDisplayHeight / 4,
+                                              PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                              nullptr, nullptr);
+        ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, ret);
+        ASSERT_EQ(OK, igbProducer->requestBuffer(slot, &buf));
+
+        uint32_t* bufData;
+        buf->lock(static_cast<uint32_t>(GraphicBuffer::USAGE_SW_WRITE_OFTEN),
+                  reinterpret_cast<void**>(&bufData));
+        fillBuffer(bufData, Rect(buf->getWidth(), buf->getHeight()), buf->getStride(), r, g, b);
+        buf->unlock();
+
+        IGraphicBufferProducer::QueueBufferOutput qbOutput;
+        IGraphicBufferProducer::QueueBufferInput input(systemTime(), true /* autotimestamp */,
+                                                       HAL_DATASPACE_UNKNOWN, {},
+                                                       NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
+                                                       Fence::NO_FENCE);
+        igbProducer->queueBuffer(slot, input, &qbOutput);
+        adapter.waitForCallbacks();
+    }
+    // capture screen and verify that it is red
+    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(r, g, b,
+                               {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight / 4}));
+
+    // update the size to half the display and dequeue a buffer quarter of the display.
+    adapter.update(mSurfaceControl, mDisplayWidth, mDisplayHeight / 2);
+
+    {
+        int slot;
+        sp<Fence> fence;
+        sp<GraphicBuffer> buf;
+        auto ret = igbProducer->dequeueBuffer(&slot, &fence, mDisplayWidth, mDisplayHeight / 8,
+                                              PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                              nullptr, nullptr);
+        ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, ret);
+        ASSERT_EQ(OK, igbProducer->requestBuffer(slot, &buf));
+
+        uint32_t* bufData;
+        buf->lock(static_cast<uint32_t>(GraphicBuffer::USAGE_SW_WRITE_OFTEN),
+                  reinterpret_cast<void**>(&bufData));
+        g = 255;
+        fillBuffer(bufData, Rect(buf->getWidth(), buf->getHeight()), buf->getStride(), r, g, b);
+        buf->unlock();
+
+        IGraphicBufferProducer::QueueBufferOutput qbOutput;
+        IGraphicBufferProducer::QueueBufferInput input(systemTime(), true /* autotimestamp */,
+                                                       HAL_DATASPACE_UNKNOWN, {},
+                                                       NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW,
+                                                       0, Fence::NO_FENCE);
+        igbProducer->queueBuffer(slot, input, &qbOutput);
+        adapter.waitForCallbacks();
+    }
+    // capture screen and verify that it is red
+    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    // verify we still scale the buffer to the new size (half the screen height)
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(r, g, b,
+                               {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight / 2}));
+}
+
 class TestProducerListener : public BnProducerListener {
 public:
     sp<IGraphicBufferProducer> mIgbp;
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index c233bf0..e0511bf 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -614,6 +614,8 @@
 
 Return<void> SensorDevice::onDynamicSensorsConnected(
         const hidl_vec<SensorInfo> &dynamicSensorsAdded) {
+    std::unique_lock<std::mutex> lock(mDynamicSensorsMutex);
+
     // Allocate a sensor_t structure for each dynamic sensor added and insert
     // it into the dictionary of connected dynamic sensors keyed by handle.
     for (size_t i = 0; i < dynamicSensorsAdded.size(); ++i) {
@@ -629,6 +631,8 @@
                 std::make_pair(sensor->handle, sensor));
     }
 
+    mDynamicSensorsCv.notify_all();
+
     return Return<void>();
 }
 
@@ -1174,8 +1178,20 @@
         dst->dynamic_sensor_meta.connected = dyn.connected;
         dst->dynamic_sensor_meta.handle = dyn.sensorHandle;
         if (dyn.connected) {
+            std::unique_lock<std::mutex> lock(mDynamicSensorsMutex);
+            // Give MAX_DYN_SENSOR_WAIT_SEC for onDynamicSensorsConnected to be invoked since it
+            // can be received out of order from this event due to a bug in the HIDL spec that
+            // marks it as oneway.
             auto it = mConnectedDynamicSensors.find(dyn.sensorHandle);
-            CHECK(it != mConnectedDynamicSensors.end());
+            if (it == mConnectedDynamicSensors.end()) {
+                mDynamicSensorsCv.wait_for(lock, MAX_DYN_SENSOR_WAIT,
+                        [&, dyn]{
+                            return mConnectedDynamicSensors.find(dyn.sensorHandle)
+                                    != mConnectedDynamicSensors.end();
+                });
+                it = mConnectedDynamicSensors.find(dyn.sensorHandle);
+                CHECK(it != mConnectedDynamicSensors.end());
+            }
 
             dst->dynamic_sensor_meta.sensor = it->second;
 
diff --git a/services/sensorservice/SensorDevice.h b/services/sensorservice/SensorDevice.h
index 75da7bb..bc8d20f 100644
--- a/services/sensorservice/SensorDevice.h
+++ b/services/sensorservice/SensorDevice.h
@@ -139,6 +139,14 @@
     Vector<sensor_t> mSensorList;
     std::unordered_map<int32_t, sensor_t*> mConnectedDynamicSensors;
 
+    // A bug in the Sensors HIDL spec which marks onDynamicSensorsConnected as oneway causes dynamic
+    // meta events and onDynamicSensorsConnected to be received out of order. This mutex + CV are
+    // used to block meta event processing until onDynamicSensorsConnected is received to simplify
+    // HAL implementations.
+    std::mutex mDynamicSensorsMutex;
+    std::condition_variable mDynamicSensorsCv;
+    static constexpr std::chrono::seconds MAX_DYN_SENSOR_WAIT{5};
+
     static const nsecs_t MINIMUM_EVENTS_PERIOD =   1000000; // 1000 Hz
     mutable Mutex mLock; // protect mActivationCount[].batchParams
     // fixed-size array after construction
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 81a669a..ee1d730 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -22,6 +22,7 @@
 #pragma clang diagnostic ignored "-Wextra"
 
 #include "RefreshRateConfigs.h"
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <utils/Trace.h>
 #include <chrono>
@@ -679,9 +680,31 @@
 RefreshRateConfigs::RefreshRateConfigs(const DisplayModes& modes, DisplayModeId currentModeId,
                                        Config config)
       : mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) {
+    initializeIdleTimer();
     updateDisplayModes(modes, currentModeId);
 }
 
+void RefreshRateConfigs::initializeIdleTimer() {
+    if (mConfig.idleTimerTimeoutMs > 0) {
+        const auto getCallback = [this]() -> std::optional<IdleTimerCallbacks::Callbacks> {
+            std::scoped_lock lock(mIdleTimerCallbacksMutex);
+            if (!mIdleTimerCallbacks.has_value()) return {};
+            return mConfig.supportKernelIdleTimer ? mIdleTimerCallbacks->kernel
+                                                  : mIdleTimerCallbacks->platform;
+        };
+
+        mIdleTimer.emplace(
+                "IdleTimer", std::chrono::milliseconds(mConfig.idleTimerTimeoutMs),
+                [getCallback] {
+                    if (const auto callback = getCallback()) callback->onReset();
+                },
+                [getCallback] {
+                    if (const auto callback = getCallback()) callback->onExpired();
+                });
+        mIdleTimer->start();
+    }
+}
+
 void RefreshRateConfigs::updateDisplayModes(const DisplayModes& modes,
                                             DisplayModeId currentModeId) {
     std::lock_guard lock(mLock);
@@ -944,6 +967,9 @@
 
     base::StringAppendF(&result, "Supports Frame Rate Override: %s\n",
                         mSupportsFrameRateOverride ? "yes" : "no");
+    base::StringAppendF(&result, "Idle timer: (%s) %s\n",
+                        mConfig.supportKernelIdleTimer ? "kernel" : "platform",
+                        mIdleTimer ? mIdleTimer->dump().c_str() : "off");
     result.append("\n");
 }
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 4a9a1fd..2addc83 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -27,6 +27,7 @@
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/HWComposer.h"
 #include "Fps.h"
+#include "Scheduler/OneShotTimer.h"
 #include "Scheduler/SchedulerUtils.h"
 #include "Scheduler/Seamlessness.h"
 #include "Scheduler/StrongTyping.h"
@@ -305,11 +306,19 @@
         // or heuristic, such that refresh rates higher than this value will not be voted for. 0 if
         // no threshold is set.
         int frameRateMultipleThreshold = 0;
+
+        // The Idle Timer timeout. 0 timeout means no idle timer.
+        int32_t idleTimerTimeoutMs = 0;
+
+        // Whether to use idle timer callbacks that support the kernel timer.
+        bool supportKernelIdleTimer = false;
     };
 
-    RefreshRateConfigs(const DisplayModes& modes, DisplayModeId currentModeId,
+    RefreshRateConfigs(const DisplayModes&, DisplayModeId,
                        Config config = {.enableFrameRateOverride = false,
-                                        .frameRateMultipleThreshold = 0});
+                                        .frameRateMultipleThreshold = 0,
+                                        .idleTimerTimeoutMs = 0,
+                                        .supportKernelIdleTimer = false});
 
     // Returns whether switching modes (refresh rate or resolution) is possible.
     // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only
@@ -345,6 +354,30 @@
                                                  Fps displayFrameRate, bool touch) const
             EXCLUDES(mLock);
 
+    bool supportsKernelIdleTimer() const { return mConfig.supportKernelIdleTimer; }
+
+    void setIdleTimerCallbacks(std::function<void()> platformTimerReset,
+                               std::function<void()> platformTimerExpired,
+                               std::function<void()> kernelTimerReset,
+                               std::function<void()> kernelTimerExpired) {
+        std::scoped_lock lock(mIdleTimerCallbacksMutex);
+        mIdleTimerCallbacks.emplace();
+        mIdleTimerCallbacks->platform.onReset = platformTimerReset;
+        mIdleTimerCallbacks->platform.onExpired = platformTimerExpired;
+        mIdleTimerCallbacks->kernel.onReset = kernelTimerReset;
+        mIdleTimerCallbacks->kernel.onExpired = kernelTimerExpired;
+    }
+
+    void resetIdleTimer(bool kernelOnly) {
+        if (!mIdleTimer) {
+            return;
+        }
+        if (kernelOnly && !mConfig.supportKernelIdleTimer) {
+            return;
+        }
+        mIdleTimer->reset();
+    };
+
     void dump(std::string& result) const EXCLUDES(mLock);
 
     RefreshRateConfigs(const RefreshRateConfigs&) = delete;
@@ -403,6 +436,8 @@
 
     void updateDisplayModes(const DisplayModes& mode, DisplayModeId currentModeId) EXCLUDES(mLock);
 
+    void initializeIdleTimer();
+
     // The list of refresh rates, indexed by display modes ID. This may change after this
     // object is initialized.
     AllRefreshRatesMapType mRefreshRates GUARDED_BY(mLock);
@@ -446,6 +481,22 @@
     };
     mutable std::optional<GetBestRefreshRateInvocation> lastBestRefreshRateInvocation
             GUARDED_BY(mLock);
+
+    // Timer that records time between requests for next vsync.
+    std::optional<scheduler::OneShotTimer> mIdleTimer;
+
+    struct IdleTimerCallbacks {
+        struct Callbacks {
+            std::function<void()> onReset;
+            std::function<void()> onExpired;
+        };
+
+        Callbacks platform;
+        Callbacks kernel;
+    };
+
+    std::mutex mIdleTimerCallbacksMutex;
+    std::optional<IdleTimerCallbacks> mIdleTimerCallbacks GUARDED_BY(mIdleTimerCallbacksMutex);
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 80b2504..1a5f0f4 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -120,28 +120,15 @@
 Scheduler::Scheduler(const std::shared_ptr<scheduler::RefreshRateConfigs>& configs,
                      ISchedulerCallback& callback)
       : Scheduler(configs, callback,
-                  {.supportKernelTimer = sysprop::support_kernel_idle_timer(false),
-                   .useContentDetection = sysprop::use_content_detection_for_refresh_rate(false)}) {
+                  {.useContentDetection = sysprop::use_content_detection_for_refresh_rate(false)}) {
 }
 
 Scheduler::Scheduler(const std::shared_ptr<scheduler::RefreshRateConfigs>& configs,
                      ISchedulerCallback& callback, Options options)
-      : Scheduler(createVsyncSchedule(options.supportKernelTimer), configs, callback,
+      : Scheduler(createVsyncSchedule(configs->supportsKernelIdleTimer()), configs, callback,
                   createLayerHistory(), options) {
     using namespace sysprop;
 
-    const int setIdleTimerMs = base::GetIntProperty("debug.sf.set_idle_timer_ms"s, 0);
-
-    if (const auto millis = setIdleTimerMs ? setIdleTimerMs : set_idle_timer_ms(0); millis > 0) {
-        const auto callback = mOptions.supportKernelTimer ? &Scheduler::kernelIdleTimerCallback
-                                                          : &Scheduler::idleTimerCallback;
-        mIdleTimer.emplace(
-                "IdleTimer", std::chrono::milliseconds(millis),
-                [this, callback] { std::invoke(callback, this, TimerState::Reset); },
-                [this, callback] { std::invoke(callback, this, TimerState::Expired); });
-        mIdleTimer->start();
-    }
-
     if (const int64_t millis = set_touch_timer_ms(0); millis > 0) {
         // Touch events are coming to SF every 100ms, so the timer needs to be higher than that
         mTouchTimer.emplace(
@@ -168,11 +155,11 @@
         mVsyncSchedule(std::move(schedule)),
         mLayerHistory(std::move(layerHistory)),
         mSchedulerCallback(schedulerCallback),
-        mRefreshRateConfigs(configs),
         mPredictedVsyncTracer(
                 base::GetBoolProperty("debug.sf.show_predicted_vsync", false)
                         ? std::make_unique<PredictedVsyncTracer>(*mVsyncSchedule.dispatch)
                         : nullptr) {
+    setRefreshRateConfigs(configs);
     mSchedulerCallback.setVsyncEnabled(false);
 }
 
@@ -180,7 +167,7 @@
     // Ensure the OneShotTimer threads are joined before we start destroying state.
     mDisplayPowerTimer.reset();
     mTouchTimer.reset();
-    mIdleTimer.reset();
+    mRefreshRateConfigs.reset();
 }
 
 Scheduler::VsyncSchedule Scheduler::createVsyncSchedule(bool supportKernelTimer) {
@@ -672,18 +659,16 @@
 }
 
 void Scheduler::resetIdleTimer() {
-    if (mIdleTimer) {
-        mIdleTimer->reset();
-    }
+    std::scoped_lock lock(mRefreshRateConfigsLock);
+    mRefreshRateConfigs->resetIdleTimer(/*kernelOnly*/ false);
 }
 
 void Scheduler::notifyTouchEvent() {
     if (mTouchTimer) {
         mTouchTimer->reset();
 
-        if (mOptions.supportKernelTimer && mIdleTimer) {
-            mIdleTimer->reset();
-        }
+        std::scoped_lock lock(mRefreshRateConfigsLock);
+        mRefreshRateConfigs->resetIdleTimer(/*kernelOnly*/ true);
     }
 }
 
@@ -755,7 +740,6 @@
 void Scheduler::dump(std::string& result) const {
     using base::StringAppendF;
 
-    StringAppendF(&result, "+  Idle timer: %s\n", mIdleTimer ? mIdleTimer->dump().c_str() : "off");
     StringAppendF(&result, "+  Touch timer: %s\n",
                   mTouchTimer ? mTouchTimer->dump().c_str() : "off");
     StringAppendF(&result, "+  Content detection: %s %s\n\n",
@@ -867,7 +851,7 @@
     }
 
     const bool touchActive = mTouchTimer && mFeatures.touch == TouchState::Active;
-    const bool idle = mIdleTimer && mFeatures.idleTimer == TimerState::Expired;
+    const bool idle = mFeatures.idleTimer == TimerState::Expired;
 
     return refreshRateConfigs
             ->getBestRefreshRate(mFeatures.contentRequirements,
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 4b6905b..2f97328 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -133,7 +133,6 @@
     // Detects content using layer history, and selects a matching refresh rate.
     void chooseRefreshRateForContent() EXCLUDES(mRefreshRateConfigsLock);
 
-    bool isIdleTimerEnabled() const { return mIdleTimer.has_value(); }
     void resetIdleTimer();
 
     // Function that resets the touch timer.
@@ -184,6 +183,15 @@
             EXCLUDES(mRefreshRateConfigsLock) {
         std::scoped_lock lock(mRefreshRateConfigsLock);
         mRefreshRateConfigs = std::move(refreshRateConfigs);
+        mRefreshRateConfigs->setIdleTimerCallbacks(
+                [this] { std::invoke(&Scheduler::idleTimerCallback, this, TimerState::Reset); },
+                [this] { std::invoke(&Scheduler::idleTimerCallback, this, TimerState::Expired); },
+                [this] {
+                    std::invoke(&Scheduler::kernelIdleTimerCallback, this, TimerState::Reset);
+                },
+                [this] {
+                    std::invoke(&Scheduler::kernelIdleTimerCallback, this, TimerState::Expired);
+                });
     }
 
     nsecs_t getVsyncPeriodFromRefreshRateConfigs() const EXCLUDES(mRefreshRateConfigsLock) {
@@ -201,8 +209,6 @@
     enum class TouchState { Inactive, Active };
 
     struct Options {
-        // Whether to use idle timer callbacks that support the kernel timer.
-        bool supportKernelTimer;
         // Whether to use content detection at all.
         bool useContentDetection;
     };
@@ -288,8 +294,6 @@
     // Used to choose refresh rate if content detection is enabled.
     std::unique_ptr<LayerHistory> mLayerHistory;
 
-    // Timer that records time between requests for next vsync.
-    std::optional<scheduler::OneShotTimer> mIdleTimer;
     // Timer used to monitor touch events.
     std::optional<scheduler::OneShotTimer> mTouchTimer;
     // Timer used to monitor display power mode.
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 7b5d462..ee973f7 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -42,6 +42,7 @@
       : mClock(std::move(clock)),
         mTracker(tracker),
         mPendingLimit(pendingFenceLimit),
+        // TODO(adyabr): change mSupportKernelIdleTimer when the active display changes
         mSupportKernelIdleTimer(supportKernelIdleTimer) {}
 
 VSyncReactor::~VSyncReactor() = default;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 9e23152..82bd398 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -275,6 +275,40 @@
     ROTATE_SURFACE_FLINGER = 0x2,
 };
 
+struct IdleTimerConfig {
+    int32_t timeoutMs;
+    bool supportKernelIdleTimer;
+};
+
+IdleTimerConfig getIdleTimerConfiguration(DisplayId displayId) {
+    // TODO(adyabr): use ro.surface_flinger.* namespace
+
+    const auto displayIdleTimerMsKey = [displayId] {
+        std::stringstream ss;
+        ss << "debug.sf.set_idle_timer_ms_" << displayId.value;
+        return ss.str();
+    }();
+
+    const auto displaySupportKernelIdleTimerKey = [displayId] {
+        std::stringstream ss;
+        ss << "debug.sf.support_kernel_idle_timer_" << displayId.value;
+        return ss.str();
+    }();
+
+    const int32_t displayIdleTimerMs = base::GetIntProperty(displayIdleTimerMsKey, 0);
+    const auto displaySupportKernelIdleTimer =
+            base::GetBoolProperty(displaySupportKernelIdleTimerKey, false);
+
+    if (displayIdleTimerMs > 0) {
+        return {displayIdleTimerMs, displaySupportKernelIdleTimer};
+    }
+
+    const int32_t setIdleTimerMs = base::GetIntProperty("debug.sf.set_idle_timer_ms", 0);
+    const int32_t millis = setIdleTimerMs ? setIdleTimerMs : sysprop::set_idle_timer_ms(0);
+
+    return {millis, sysprop::support_kernel_idle_timer(false)};
+}
+
 }  // namespace anonymous
 
 // ---------------------------------------------------------------------------
@@ -469,9 +503,6 @@
 
     useFrameRateApi = use_frame_rate_api(true);
 
-    mKernelIdleTimerEnabled = mSupportKernelIdleTimer = sysprop::support_kernel_idle_timer(false);
-    base::SetProperty(KERNEL_IDLE_TIMER_PROP, mKernelIdleTimerEnabled ? "true" : "false");
-
     mRefreshRateOverlaySpinner = property_get_bool("sf.debug.show_refresh_rate_overlay_spinner", 0);
 
     // Debug property overrides ro. property
@@ -2643,10 +2674,14 @@
         creationArgs.connectionType = physical->type;
         creationArgs.supportedModes = physical->supportedModes;
         creationArgs.activeModeId = physical->activeMode->getId();
+        const auto [idleTimerTimeoutMs, supportKernelIdleTimer] =
+                getIdleTimerConfiguration(compositionDisplay->getId());
         scheduler::RefreshRateConfigs::Config config =
                 {.enableFrameRateOverride = android::sysprop::enable_frame_rate_override(false),
                  .frameRateMultipleThreshold =
-                         base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0)};
+                         base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0),
+                 .idleTimerTimeoutMs = idleTimerTimeoutMs,
+                 .supportKernelIdleTimer = supportKernelIdleTimer};
         creationArgs.refreshRateConfigs =
                 std::make_shared<scheduler::RefreshRateConfigs>(creationArgs.supportedModes,
                                                                 creationArgs.activeModeId, config);
@@ -5875,16 +5910,18 @@
 void SurfaceFlinger::toggleKernelIdleTimer() {
     using KernelIdleTimerAction = scheduler::RefreshRateConfigs::KernelIdleTimerAction;
 
-    // If the support for kernel idle timer is disabled in SF code, don't do anything.
-    if (!mSupportKernelIdleTimer) {
-        return;
-    }
     const auto display = getDefaultDisplayDeviceLocked();
     if (!display) {
         ALOGW("%s: default display is null", __func__);
         return;
     }
 
+    // If the support for kernel idle timer is disabled for the active display,
+    // don't do anything.
+    if (!display->refreshRateConfigs().supportsKernelIdleTimer()) {
+        return;
+    }
+
     const KernelIdleTimerAction action = display->refreshRateConfigs().getIdleTimerAction();
     switch (action) {
         case KernelIdleTimerAction::TurnOff:
@@ -7015,6 +7052,10 @@
     mScheduler->setRefreshRateConfigs(activeDisplay->holdRefreshRateConfigs());
     onActiveDisplaySizeChanged(activeDisplay);
     mActiveDisplayTransformHint = activeDisplay->getTransformHint();
+
+    // Update the kernel timer for the current active display, since the policy
+    // for this display might have changed when it was not the active display.
+    toggleKernelIdleTimer();
 }
 
 status_t SurfaceFlinger::addWindowInfosListener(
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 89c1d50..dba2bd6 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -759,8 +759,6 @@
     // Keeps track of whether the kernel idle timer is currently enabled, so we don't have to
     // make calls to sys prop each time.
     bool mKernelIdleTimerEnabled = false;
-    // Keeps track of whether the kernel timer is supported on the SF side.
-    bool mSupportKernelIdleTimer = false;
     // Show spinner with refresh rate overlay
     bool mRefreshRateOverlaySpinner = false;
 
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index a99dabe..1d21bd4 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -44,7 +44,7 @@
                       ISchedulerCallback& callback)
           : Scheduler({std::move(vsyncController), std::move(vsyncTracker), nullptr},
                       refreshRateConfigs, callback, createLayerHistory(),
-                      {.supportKernelTimer = false, .useContentDetection = true}) {}
+                      {.useContentDetection = true}) {}
 
     // Used to inject mock event thread.
     ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {