[SF] Kernel idle timer configuration

This change configures the kernel idle timer using the composer3 HAL APIs.

BUG: 198808492
Test: atest libsurfaceflinger_unittest
Change-Id: I7de140c3094cb84c8efe782c19c98e126cb4c60b
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 719f15c..1f1dd1a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -133,6 +133,8 @@
                  status_t(PhysicalDisplayId,
                           std::optional<aidl::android::hardware::graphics::common::
                                                 DisplayDecorationSupport>* support));
+    MOCK_METHOD2(setIdleTimerEnabled, status_t(PhysicalDisplayId, std::chrono::milliseconds));
+    MOCK_METHOD1(hasDisplayIdleTimerCapability, bool(PhysicalDisplayId displayId));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 80e2d99..92592f7 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -248,6 +248,7 @@
         case OptionalFeature::ExpectedPresentTime:
         case OptionalFeature::DisplayBrightnessCommand:
         case OptionalFeature::BootDisplayConfig:
+        case OptionalFeature::KernelIdleTimer:
             return true;
     }
 }
@@ -475,6 +476,19 @@
     return Error::NONE;
 }
 
+Error AidlComposer::hasDisplayIdleTimerCapability(Display display, bool* outSupport) {
+    std::vector<AidlDisplayCapability> capabilities;
+    const auto status =
+            mAidlComposerClient->getDisplayCapabilities(translate<int64_t>(display), &capabilities);
+    if (!status.isOk()) {
+        ALOGE("getDisplayCapabilities failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    *outSupport = std::find(capabilities.begin(), capabilities.end(),
+                            AidlDisplayCapability::DISPLAY_IDLE_TIMER) != capabilities.end();
+    return Error::NONE;
+}
+
 Error AidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outTypes,
                                        float* outMaxLuminance, float* outMaxAverageLuminance,
                                        float* outMinLuminance) {
@@ -1100,5 +1114,17 @@
     }
     return Error::NONE;
 }
+
+Error AidlComposer::setIdleTimerEnabled(Display displayId, std::chrono::milliseconds timeout) {
+    const auto status =
+            mAidlComposerClient->setIdleTimerEnabled(translate<int64_t>(displayId),
+                                                     translate<int32_t>(timeout.count()));
+    if (!status.isOk()) {
+        ALOGE("setIdleTimerEnabled failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
 } // namespace Hwc2
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 80ca8da..6c0f636 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -100,6 +100,7 @@
                              std::vector<uint32_t>* outLayerRequestMasks) override;
 
     Error getDozeSupport(Display display, bool* outSupport) override;
+    Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override;
     Error getHdrCapabilities(Display display, std::vector<Hdr>* outTypes, float* outMaxLuminance,
                              float* outMaxAverageLuminance, float* outMinLuminance) override;
 
@@ -220,6 +221,7 @@
     Error getPreferredBootDisplayConfig(Display displayId, Config*) override;
     Error getDisplayDecorationSupport(Display display,
                                       std::optional<DisplayDecorationSupport>* support) override;
+    Error setIdleTimerEnabled(Display displayId, std::chrono::milliseconds timeout) override;
 
 private:
     // Many public functions above simply write a command into the command
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 23886d4..6abe7d1 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -93,6 +93,7 @@
         // Whether setDisplayBrightness is able to be applied as part of a display command.
         DisplayBrightnessCommand,
         BootDisplayConfig,
+        KernelIdleTimer,
     };
 
     virtual bool isSupported(OptionalFeature) const = 0;
@@ -134,6 +135,7 @@
                                      std::vector<uint32_t>* outLayerRequestMasks) = 0;
 
     virtual Error getDozeSupport(Display display, bool* outSupport) = 0;
+    virtual Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) = 0;
     virtual Error getHdrCapabilities(Display display, std::vector<Hdr>* outTypes,
                                      float* outMaxLuminance, float* outMaxAverageLuminance,
                                      float* outMinLuminance) = 0;
@@ -274,6 +276,7 @@
             Display display,
             std::optional<::aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                     support) = 0;
+    virtual Error setIdleTimerEnabled(Display displayId, std::chrono::milliseconds timeout) = 0;
 };
 
 } // namespace Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index a1b663c..6501276 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -148,6 +148,12 @@
     return mComposer.isSupported(android::Hwc2::Composer::OptionalFeature::RefreshRateSwitching);
 }
 
+bool Display::hasDisplayIdleTimerCapability() const {
+    bool isCapabilitySupported = false;
+    return mComposer.hasDisplayIdleTimerCapability(mId, &isCapabilitySupported) == Error::NONE &&
+            isCapabilitySupported;
+}
+
 Error Display::getChangedCompositionTypes(std::unordered_map<HWC2::Layer*, Composition>* outTypes) {
     std::vector<Hwc2::Layer> layerIds;
     std::vector<Composition> types;
@@ -588,6 +594,11 @@
     return static_cast<Error>(error);
 }
 
+Error Display::setIdleTimerEnabled(std::chrono::milliseconds timeout) {
+    const auto error = mComposer.setIdleTimerEnabled(mId, timeout);
+    return static_cast<Error>(error);
+}
+
 // For use by Device
 
 void Display::setConnected(bool connected) {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 334d6ec..c03cede 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -91,6 +91,7 @@
     virtual bool hasCapability(
             aidl::android::hardware::graphics::composer3::DisplayCapability) const = 0;
     virtual bool isVsyncPeriodSwitchSupported() const = 0;
+    virtual bool hasDisplayIdleTimerCapability() const = 0;
     virtual void onLayerDestroyed(hal::HWLayerId layerId) = 0;
 
     [[clang::warn_unused_result]] virtual hal::Error acceptChanges() = 0;
@@ -166,6 +167,8 @@
     [[clang::warn_unused_result]] virtual hal::Error getDisplayDecorationSupport(
             std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                     support) = 0;
+    [[clang::warn_unused_result]] virtual hal::Error setIdleTimerEnabled(
+            std::chrono::milliseconds timeout) = 0;
 };
 
 namespace impl {
@@ -242,6 +245,7 @@
     hal::Error getDisplayDecorationSupport(
             std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                     support) override;
+    hal::Error setIdleTimerEnabled(std::chrono::milliseconds timeout) override;
 
     // Other Display methods
     hal::HWDisplayId getId() const override { return mId; }
@@ -250,6 +254,7 @@
     bool hasCapability(aidl::android::hardware::graphics::composer3::DisplayCapability)
             const override EXCLUDES(mDisplayCapabilitiesMutex);
     bool isVsyncPeriodSwitchSupported() const override;
+    bool hasDisplayIdleTimerCapability() const override;
     void onLayerDestroyed(hal::HWLayerId layerId) override;
 
 private:
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index ed6e4b0..02b3772 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -971,6 +971,26 @@
     }
 }
 
+status_t HWComposer::setIdleTimerEnabled(PhysicalDisplayId displayId,
+                                         std::chrono::milliseconds timeout) {
+    ATRACE_CALL();
+    RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
+    const auto error = mDisplayData[displayId].hwcDisplay->setIdleTimerEnabled(timeout);
+    if (error == hal::Error::UNSUPPORTED) {
+        RETURN_IF_HWC_ERROR(error, displayId, INVALID_OPERATION);
+    }
+    if (error == hal::Error::BAD_PARAMETER) {
+        RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
+    }
+    RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
+    return NO_ERROR;
+}
+
+bool HWComposer::hasDisplayIdleTimerCapability(PhysicalDisplayId displayId) {
+    RETURN_IF_INVALID_DISPLAY(displayId, false);
+    return mDisplayData[displayId].hwcDisplay->hasDisplayIdleTimerCapability();
+}
+
 void HWComposer::loadLayerMetadataSupport() {
     mSupportedLayerGenericMetadata.clear();
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 5b2e265..f9637f0 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -267,6 +267,8 @@
             PhysicalDisplayId,
             std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                     support) = 0;
+    virtual status_t setIdleTimerEnabled(PhysicalDisplayId, std::chrono::milliseconds timeout) = 0;
+    virtual bool hasDisplayIdleTimerCapability(PhysicalDisplayId) = 0;
 };
 
 namespace impl {
@@ -402,6 +404,8 @@
             PhysicalDisplayId,
             std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                     support) override;
+    status_t setIdleTimerEnabled(PhysicalDisplayId, std::chrono::milliseconds timeout) override;
+    bool hasDisplayIdleTimerCapability(PhysicalDisplayId) override;
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index f735bba..33adceb 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -236,6 +236,7 @@
         case OptionalFeature::ExpectedPresentTime:
         case OptionalFeature::DisplayBrightnessCommand:
         case OptionalFeature::BootDisplayConfig:
+        case OptionalFeature::KernelIdleTimer:
             return false;
     }
 }
@@ -487,6 +488,11 @@
     return error;
 }
 
+Error HidlComposer::hasDisplayIdleTimerCapability(Display, bool*) {
+    LOG_ALWAYS_FATAL("hasDisplayIdleTimerCapability should have never been called on this as "
+                     "OptionalFeature::KernelIdleTimer is not supported on HIDL");
+}
+
 Error HidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outTypes,
                                        float* outMaxLuminance, float* outMaxAverageLuminance,
                                        float* outMinLuminance) {
@@ -1320,6 +1326,11 @@
     return Error::UNSUPPORTED;
 }
 
+Error HidlComposer::setIdleTimerEnabled(Display, std::chrono::milliseconds) {
+    LOG_ALWAYS_FATAL("setIdleTimerEnabled should have never been called on this as "
+                     "OptionalFeature::KernelIdleTimer is not supported on HIDL");
+}
+
 void HidlComposer::registerCallback(ComposerCallback& callback) {
     const bool vsyncSwitchingSupported =
             isSupported(Hwc2::Composer::OptionalFeature::RefreshRateSwitching);
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index c2b60cb..a1ea4f2 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -208,6 +208,7 @@
                              std::vector<uint32_t>* outLayerRequestMasks) override;
 
     Error getDozeSupport(Display display, bool* outSupport) override;
+    Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override;
     Error getHdrCapabilities(Display display, std::vector<Hdr>* outTypes, float* outMaxLuminance,
                              float* outMaxAverageLuminance, float* outMinLuminance) override;
 
@@ -331,6 +332,7 @@
             Display display,
             std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                     support) override;
+    Error setIdleTimerEnabled(Display displayId, std::chrono::milliseconds timeout) override;
 
 private:
     class CommandWriter : public CommandWriterBase {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 15e30b3..65c8613 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -670,9 +670,9 @@
 }
 
 void RefreshRateConfigs::initializeIdleTimer() {
-    if (mConfig.idleTimerTimeoutMs > 0) {
+    if (mConfig.idleTimerTimeout > 0ms) {
         mIdleTimer.emplace(
-                "IdleTimer", std::chrono::milliseconds(mConfig.idleTimerTimeoutMs),
+                "IdleTimer", mConfig.idleTimerTimeout,
                 [this] {
                     std::scoped_lock lock(mIdleTimerCallbacksMutex);
                     if (const auto callbacks = getIdleTimerCallbacks()) {
@@ -963,12 +963,24 @@
 
     base::StringAppendF(&result, "Supports Frame Rate Override By Content: %s\n",
                         mSupportsFrameRateOverrideByContent ? "yes" : "no");
-    base::StringAppendF(&result, "Idle timer: (%s) %s\n",
-                        mConfig.supportKernelIdleTimer ? "kernel" : "platform",
-                        mIdleTimer ? mIdleTimer->dump().c_str() : "off");
+    base::StringAppendF(&result, "Idle timer: ");
+    if (mConfig.kernelIdleTimerController.has_value()) {
+        if (mConfig.kernelIdleTimerController == KernelIdleTimerController::Sysprop) {
+            base::StringAppendF(&result, "(kernel(sysprop))");
+        } else {
+            base::StringAppendF(&result, "(kernel(hwc))");
+        }
+    } else {
+        base::StringAppendF(&result, "(platform)");
+    }
+    base::StringAppendF(&result, " %s\n", mIdleTimer ? mIdleTimer->dump().c_str() : "off");
     result.append("\n");
 }
 
+std::chrono::milliseconds RefreshRateConfigs::getIdleTimerTimeout() {
+    return mConfig.idleTimerTimeout;
+}
+
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 14583e3..30d3edd 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -272,6 +272,8 @@
     // Returns a known frame rate that is the closest to frameRate
     Fps findClosestKnownFrameRate(Fps frameRate) const;
 
+    enum class KernelIdleTimerController { Sysprop, HwcApi };
+
     // Configuration flags.
     struct Config {
         bool enableFrameRateOverride = false;
@@ -282,17 +284,18 @@
         int frameRateMultipleThreshold = 0;
 
         // The Idle Timer timeout. 0 timeout means no idle timer.
-        int32_t idleTimerTimeoutMs = 0;
+        std::chrono::milliseconds idleTimerTimeout = 0ms;
 
-        // Whether to use idle timer callbacks that support the kernel timer.
-        bool supportKernelIdleTimer = false;
+        // The controller representing how the kernel idle timer will be configured
+        // either on the HWC api or sysprop.
+        std::optional<KernelIdleTimerController> kernelIdleTimerController;
     };
 
     RefreshRateConfigs(const DisplayModes&, DisplayModeId,
                        Config config = {.enableFrameRateOverride = false,
                                         .frameRateMultipleThreshold = 0,
-                                        .idleTimerTimeoutMs = 0,
-                                        .supportKernelIdleTimer = false});
+                                        .idleTimerTimeout = 0ms,
+                                        .kernelIdleTimerController = {}});
 
     RefreshRateConfigs(const RefreshRateConfigs&) = delete;
     RefreshRateConfigs& operator=(const RefreshRateConfigs&) = delete;
@@ -310,6 +313,7 @@
         TurnOff,  // Turn off the idle timer.
         TurnOn    // Turn on the idle timer.
     };
+
     // Checks whether kernel idle timer should be active depending the policy decisions around
     // refresh rates.
     KernelIdleTimerAction getIdleTimerAction() const;
@@ -332,7 +336,9 @@
                                                  Fps displayFrameRate, GlobalSignals) const
             EXCLUDES(mLock);
 
-    bool supportsKernelIdleTimer() const { return mConfig.supportKernelIdleTimer; }
+    std::optional<KernelIdleTimerController> kernelIdleTimerController() {
+        return mConfig.kernelIdleTimerController;
+    }
 
     struct IdleTimerCallbacks {
         struct Callbacks {
@@ -370,7 +376,7 @@
         if (!mIdleTimer) {
             return;
         }
-        if (kernelOnly && !mConfig.supportKernelIdleTimer) {
+        if (kernelOnly && !mConfig.kernelIdleTimerController.has_value()) {
             return;
         }
         mIdleTimer->reset();
@@ -378,6 +384,8 @@
 
     void dump(std::string& result) const EXCLUDES(mLock);
 
+    std::chrono::milliseconds getIdleTimerTimeout();
+
 private:
     friend struct TestableRefreshRateConfigs;
 
@@ -437,8 +445,8 @@
     std::optional<IdleTimerCallbacks::Callbacks> getIdleTimerCallbacks() const
             REQUIRES(mIdleTimerCallbacksMutex) {
         if (!mIdleTimerCallbacks) return {};
-        return mConfig.supportKernelIdleTimer ? mIdleTimerCallbacks->kernel
-                                              : mIdleTimerCallbacks->platform;
+        return mConfig.kernelIdleTimerController.has_value() ? mIdleTimerCallbacks->kernel
+                                                             : mIdleTimerCallbacks->platform;
     }
 
     // The list of refresh rates, indexed by display modes ID. This may change after this
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 089875c..425b78b 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -169,6 +169,8 @@
 using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
+using KernelIdleTimerController =
+        ::android::scheduler::RefreshRateConfigs::KernelIdleTimerController;
 
 namespace android {
 
@@ -270,39 +272,33 @@
     return dataspace == Dataspace::V0_SRGB || dataspace == Dataspace::DISPLAY_P3;
 }
 
-
-struct IdleTimerConfig {
-    int32_t timeoutMs;
-    bool supportKernelIdleTimer;
-};
-
-IdleTimerConfig getIdleTimerConfiguration(DisplayId displayId) {
-    // TODO(adyabr): use ro.surface_flinger.* namespace
-
+std::chrono::milliseconds getIdleTimerTimeout(DisplayId displayId) {
     const auto displayIdleTimerMsKey = [displayId] {
         std::stringstream ss;
         ss << "debug.sf.set_idle_timer_ms_" << displayId.value;
         return ss.str();
     }();
 
+    const int32_t displayIdleTimerMs = base::GetIntProperty(displayIdleTimerMsKey, 0);
+    if (displayIdleTimerMs > 0) {
+        return std::chrono::milliseconds(displayIdleTimerMs);
+    }
+
+    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 std::chrono::milliseconds(millis);
+}
+
+bool getKernelIdleTimerSyspropConfig(DisplayId displayId) {
     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)};
+    return displaySupportKernelIdleTimer || sysprop::support_kernel_idle_timer(false);
 }
 
 }  // namespace anonymous
@@ -2829,14 +2825,15 @@
         creationArgs.connectionType = physical->type;
         creationArgs.supportedModes = physical->supportedModes;
         creationArgs.activeModeId = physical->activeMode->getId();
-        const auto [idleTimerTimeoutMs, supportKernelIdleTimer] =
-                getIdleTimerConfiguration(compositionDisplay->getId());
+        const auto [kernelIdleTimerController, idleTimerTimeoutMs] =
+                getKernelIdleTimerProperties(compositionDisplay->getId());
+
         scheduler::RefreshRateConfigs::Config config =
                 {.enableFrameRateOverride = android::sysprop::enable_frame_rate_override(false),
                  .frameRateMultipleThreshold =
                          base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0),
-                 .idleTimerTimeoutMs = idleTimerTimeoutMs,
-                 .supportKernelIdleTimer = supportKernelIdleTimer};
+                 .idleTimerTimeout = idleTimerTimeoutMs,
+                 .kernelIdleTimerController = kernelIdleTimerController};
         creationArgs.refreshRateConfigs =
                 std::make_shared<scheduler::RefreshRateConfigs>(creationArgs.supportedModes,
                                                                 creationArgs.activeModeId, config);
@@ -3410,7 +3407,7 @@
                                                         features);
     {
         auto configs = display->holdRefreshRateConfigs();
-        if (configs->supportsKernelIdleTimer()) {
+        if (configs->kernelIdleTimerController().has_value()) {
             features |= Feature::kKernelIdleTimer;
         }
 
@@ -6148,6 +6145,47 @@
     }));
 }
 
+std::pair<std::optional<KernelIdleTimerController>, std::chrono::milliseconds>
+SurfaceFlinger::getKernelIdleTimerProperties(DisplayId displayId) {
+    const bool isKernelIdleTimerHwcSupported = getHwComposer().getComposer()->isSupported(
+            android::Hwc2::Composer::OptionalFeature::KernelIdleTimer);
+    const auto timeout = getIdleTimerTimeout(displayId);
+    if (isKernelIdleTimerHwcSupported) {
+        if (const auto id = PhysicalDisplayId::tryCast(displayId);
+            getHwComposer().hasDisplayIdleTimerCapability(*id)) {
+            // In order to decide if we can use the HWC api for idle timer
+            // we query DisplayCapability::DISPLAY_IDLE_TIMER directly on the composer
+            // without relying on hasDisplayCapability.
+            // hasDisplayCapability relies on DisplayCapabilities
+            // which are updated after we set the PowerMode::ON.
+            // DISPLAY_IDLE_TIMER is a display driver property
+            // and is available before the PowerMode::ON
+            return {KernelIdleTimerController::HwcApi, timeout};
+        }
+        return {std::nullopt, timeout};
+    }
+    if (getKernelIdleTimerSyspropConfig(displayId)) {
+        return {KernelIdleTimerController::Sysprop, timeout};
+    }
+
+    return {std::nullopt, timeout};
+}
+
+void SurfaceFlinger::updateKernelIdleTimer(std::chrono::milliseconds timeout,
+                                           KernelIdleTimerController controller,
+                                           PhysicalDisplayId displayId) {
+    switch (controller) {
+        case KernelIdleTimerController::HwcApi: {
+            getHwComposer().setIdleTimerEnabled(displayId, timeout);
+            break;
+        }
+        case KernelIdleTimerController::Sysprop: {
+            base::SetProperty(KERNEL_IDLE_TIMER_PROP, timeout > 0ms ? "true" : "false");
+            break;
+        }
+    }
+}
+
 void SurfaceFlinger::toggleKernelIdleTimer() {
     using KernelIdleTimerAction = scheduler::RefreshRateConfigs::KernelIdleTimerAction;
 
@@ -6159,23 +6197,31 @@
 
     // If the support for kernel idle timer is disabled for the active display,
     // don't do anything.
-    if (!display->refreshRateConfigs().supportsKernelIdleTimer()) {
+    const std::optional<KernelIdleTimerController> kernelIdleTimerController =
+            display->refreshRateConfigs().kernelIdleTimerController();
+    if (!kernelIdleTimerController.has_value()) {
         return;
     }
 
     const KernelIdleTimerAction action = display->refreshRateConfigs().getIdleTimerAction();
+
     switch (action) {
         case KernelIdleTimerAction::TurnOff:
             if (mKernelIdleTimerEnabled) {
                 ATRACE_INT("KernelIdleTimer", 0);
-                base::SetProperty(KERNEL_IDLE_TIMER_PROP, "false");
+                std::chrono::milliseconds constexpr kTimerDisabledTimeout = 0ms;
+                updateKernelIdleTimer(kTimerDisabledTimeout, kernelIdleTimerController.value(),
+                                      display->getPhysicalId());
                 mKernelIdleTimerEnabled = false;
             }
             break;
         case KernelIdleTimerAction::TurnOn:
             if (!mKernelIdleTimerEnabled) {
                 ATRACE_INT("KernelIdleTimer", 1);
-                base::SetProperty(KERNEL_IDLE_TIMER_PROP, "true");
+                const std::chrono::milliseconds timeout =
+                        display->refreshRateConfigs().getIdleTimerTimeout();
+                updateKernelIdleTimer(timeout, kernelIdleTimerController.value(),
+                                      display->getPhysicalId());
                 mKernelIdleTimerEnabled = true;
             }
             break;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index f5a695b..fa65803 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -462,6 +462,8 @@
     };
 
     using ActiveModeInfo = DisplayDevice::ActiveModeInfo;
+    using KernelIdleTimerController =
+            ::android::scheduler::RefreshRateConfigs::KernelIdleTimerController;
 
     enum class BootStage {
         BOOTLOADER,
@@ -685,6 +687,14 @@
     void triggerOnFrameRateOverridesChanged() override;
     // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates.
     void toggleKernelIdleTimer() REQUIRES(mStateLock);
+    // Get the controller and timeout that will help decide how the kernel idle timer will be
+    // configured and what value to use as the timeout.
+    std::pair<std::optional<KernelIdleTimerController>, std::chrono::milliseconds>
+            getKernelIdleTimerProperties(DisplayId) REQUIRES(mStateLock);
+    // Updates the kernel idle timer either through HWC or through sysprop
+    // depending on which controller is provided
+    void updateKernelIdleTimer(std::chrono::milliseconds timeoutMs, KernelIdleTimerController,
+                               PhysicalDisplayId) REQUIRES(mStateLock);
     // 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;
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index a1aa7e8..996f835 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -76,6 +76,7 @@
     MOCK_METHOD4(getDisplayRequests,
                  Error(Display, uint32_t*, std::vector<Layer>*, std::vector<uint32_t>*));
     MOCK_METHOD2(getDozeSupport, Error(Display, bool*));
+    MOCK_METHOD2(getKernelIdleTimerSupport, Error(Display, bool*));
     MOCK_METHOD5(getHdrCapabilities, Error(Display, std::vector<Hdr>*, float*, float*, float*));
     MOCK_METHOD1(getPerFrameMetadataKeys,
                  std::vector<IComposerClient::PerFrameMetadataKey>(Display));
@@ -157,6 +158,8 @@
                  Error(Display, Layer, const std::vector<IComposerClient::Rect>&));
     MOCK_METHOD2(getDisplayDecorationSupport,
                  Error(Display, std::optional<DisplayDecorationSupport>*));
+    MOCK_METHOD2(setIdleTimerEnabled, Error(Display, std::chrono::milliseconds));
+    MOCK_METHOD2(hasDisplayIdleTimerCapability, Error(Display, bool*));
 };
 
 } // namespace Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index 570ffeb..d9faa06 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -99,6 +99,8 @@
             hal::Error, getDisplayDecorationSupport,
             (std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport> *),
             (override));
+    MOCK_METHOD(hal::Error, setIdleTimerEnabled, (std::chrono::milliseconds), (override));
+    MOCK_METHOD(bool, hasDisplayIdleTimerCapability, (), (const override));
 };
 
 class Layer : public HWC2::Layer {