Change to use support info for interval millis

Add unit tests and fix bugs on no result handling

Bug: 346604998
Flag: android.os.cpu_gpu_headrooms
Test: atest NativeSystemHealthUnitTest SystemHealthManagerUnitTest
            NativeSystemHealthTest HeadroomTest
Change-Id: I7a820c7ccfcbe4c3db2ae0ec81a66c7a55c9add3
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index a1e9cf2..d9b7249 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -344,11 +344,7 @@
                 || !mHintManagerClientData.supportInfo.headroom.isCpuSupported) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return mHintManager.getCpuHeadroomMinIntervalMillis();
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
+        return mHintManagerClientData.supportInfo.headroom.cpuMinIntervalMillis;
     }
 
     /**
@@ -366,11 +362,7 @@
                 || !mHintManagerClientData.supportInfo.headroom.isGpuSupported) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return mHintManager.getGpuHeadroomMinIntervalMillis();
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
+        return mHintManagerClientData.supportInfo.headroom.gpuMinIntervalMillis;
     }
 
     /**
diff --git a/core/tests/coretests/src/android/os/SystemHealthManagerUnitTest.java b/core/tests/coretests/src/android/os/SystemHealthManagerUnitTest.java
new file mode 100644
index 0000000..e093e1a
--- /dev/null
+++ b/core/tests/coretests/src/android/os/SystemHealthManagerUnitTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.CpuHeadroomResult;
+import android.hardware.power.GpuHeadroomResult;
+import android.hardware.power.SupportInfo;
+import android.os.health.SystemHealthManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.app.IBatteryStats;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class SystemHealthManagerUnitTest {
+    @Mock
+    private IBatteryStats mBatteryStats;
+    @Mock
+    private IPowerStatsService mPowerStats;
+    @Mock
+    private IHintManager mHintManager;
+    private SystemHealthManager mSystemHealthManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        IHintManager.HintManagerClientData clientData = new IHintManager.HintManagerClientData();
+        clientData.supportInfo = new SupportInfo();
+        clientData.maxCpuHeadroomThreads = 10;
+        clientData.supportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
+        clientData.supportInfo.headroom.isCpuSupported = true;
+        clientData.supportInfo.headroom.isGpuSupported = true;
+        clientData.supportInfo.headroom.cpuMinCalculationWindowMillis = 45;
+        clientData.supportInfo.headroom.cpuMaxCalculationWindowMillis = 9999;
+        clientData.supportInfo.headroom.gpuMinCalculationWindowMillis = 46;
+        clientData.supportInfo.headroom.gpuMaxCalculationWindowMillis = 9998;
+        clientData.supportInfo.headroom.cpuMinIntervalMillis = 999;
+        clientData.supportInfo.headroom.gpuMinIntervalMillis = 998;
+        when(mHintManager.getClientData()).thenReturn(clientData);
+        mSystemHealthManager = new SystemHealthManager(mBatteryStats, mPowerStats, mHintManager);
+    }
+
+    @Test
+    public void testHeadroomParamsValueRange() {
+        assertEquals(999, mSystemHealthManager.getCpuHeadroomMinIntervalMillis());
+        assertEquals(998, mSystemHealthManager.getGpuHeadroomMinIntervalMillis());
+        assertEquals(45, (int) mSystemHealthManager.getCpuHeadroomCalculationWindowRange().first);
+        assertEquals(9999,
+                (int) mSystemHealthManager.getCpuHeadroomCalculationWindowRange().second);
+        assertEquals(46, (int) mSystemHealthManager.getGpuHeadroomCalculationWindowRange().first);
+        assertEquals(9998,
+                (int) mSystemHealthManager.getGpuHeadroomCalculationWindowRange().second);
+        assertEquals(10, (int) mSystemHealthManager.getMaxCpuHeadroomTidsSize());
+    }
+
+    @Test
+    public void testGetCpuHeadroom() throws RemoteException, InterruptedException {
+        final CpuHeadroomParams params1 = null;
+        final CpuHeadroomParamsInternal internalParams1 = new CpuHeadroomParamsInternal();
+
+        final CpuHeadroomParams params2 = new CpuHeadroomParams.Builder()
+                .setCalculationWindowMillis(100)
+                .build();
+        final CpuHeadroomParamsInternal internalParams2 = new CpuHeadroomParamsInternal();
+        internalParams2.calculationWindowMillis = 100;
+
+        final CpuHeadroomParams params3 = new CpuHeadroomParams.Builder()
+                .setCalculationType(CpuHeadroomParams.CPU_HEADROOM_CALCULATION_TYPE_AVERAGE)
+                .build();
+        final CpuHeadroomParamsInternal internalParams3 = new CpuHeadroomParamsInternal();
+        internalParams3.calculationType =
+                (byte) CpuHeadroomParams.CPU_HEADROOM_CALCULATION_TYPE_AVERAGE;
+
+        final CpuHeadroomParams params4 = new CpuHeadroomParams.Builder()
+                .setTids(1000, 1001)
+                .build();
+        final CpuHeadroomParamsInternal internalParams4 = new CpuHeadroomParamsInternal();
+        internalParams4.tids = new int[]{1000, 1001};
+
+        when(mHintManager.getCpuHeadroom(internalParams1)).thenReturn(
+                CpuHeadroomResult.globalHeadroom(99f));
+        when(mHintManager.getCpuHeadroom(internalParams2)).thenReturn(
+                CpuHeadroomResult.globalHeadroom(98f));
+        when(mHintManager.getCpuHeadroom(internalParams3)).thenReturn(
+                CpuHeadroomResult.globalHeadroom(97f));
+        when(mHintManager.getCpuHeadroom(internalParams4)).thenReturn(null);
+
+        assertEquals(99f, mSystemHealthManager.getCpuHeadroom(params1), 0.001f);
+        assertEquals(98f, mSystemHealthManager.getCpuHeadroom(params2), 0.001f);
+        assertEquals(97f, mSystemHealthManager.getCpuHeadroom(params3), 0.001f);
+        assertTrue(Float.isNaN(mSystemHealthManager.getCpuHeadroom(params4)));
+        verify(mHintManager, times(1)).getCpuHeadroom(internalParams1);
+        verify(mHintManager, times(1)).getCpuHeadroom(internalParams2);
+        verify(mHintManager, times(1)).getCpuHeadroom(internalParams3);
+        verify(mHintManager, times(1)).getCpuHeadroom(internalParams4);
+    }
+
+    @Test
+    public void testGetGpuHeadroom() throws RemoteException, InterruptedException {
+        final GpuHeadroomParams params1 = null;
+        final GpuHeadroomParamsInternal internalParams1 = new GpuHeadroomParamsInternal();
+        final GpuHeadroomParams params2 = new GpuHeadroomParams.Builder()
+                .setCalculationWindowMillis(100)
+                .build();
+        final GpuHeadroomParamsInternal internalParams2 = new GpuHeadroomParamsInternal();
+        internalParams2.calculationWindowMillis = 100;
+        final GpuHeadroomParams params3 = new GpuHeadroomParams.Builder()
+                .setCalculationType(GpuHeadroomParams.GPU_HEADROOM_CALCULATION_TYPE_AVERAGE)
+                .build();
+        final GpuHeadroomParamsInternal internalParams3 = new GpuHeadroomParamsInternal();
+        internalParams3.calculationType =
+                (byte) GpuHeadroomParams.GPU_HEADROOM_CALCULATION_TYPE_AVERAGE;
+
+        when(mHintManager.getGpuHeadroom(internalParams1)).thenReturn(
+                GpuHeadroomResult.globalHeadroom(99f));
+        when(mHintManager.getGpuHeadroom(internalParams2)).thenReturn(
+                GpuHeadroomResult.globalHeadroom(98f));
+        when(mHintManager.getGpuHeadroom(internalParams3)).thenReturn(null);
+
+        assertEquals(99f, mSystemHealthManager.getGpuHeadroom(params1), 0.001f);
+        assertEquals(98f, mSystemHealthManager.getGpuHeadroom(params2), 0.001f);
+        assertTrue(Float.isNaN(mSystemHealthManager.getGpuHeadroom(params3)));
+        verify(mHintManager, times(1)).getGpuHeadroom(internalParams1);
+        verify(mHintManager, times(1)).getGpuHeadroom(internalParams2);
+        verify(mHintManager, times(1)).getGpuHeadroom(internalParams3);
+    }
+}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b30b779..49cbd71 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -421,6 +421,7 @@
 LIBANDROID_PLATFORM {
   global:
     AThermal_setIThermalServiceForTesting;
+    ASystemHealth_setIHintManagerForTesting;
     APerformanceHint_setIHintManagerForTesting;
     APerformanceHint_sendHint;
     APerformanceHint_getThreadIds;
diff --git a/native/android/system_health.cpp b/native/android/system_health.cpp
index 5c07ac7..1b43e71 100644
--- a/native/android/system_health.cpp
+++ b/native/android/system_health.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "system_health"
+
 #include <aidl/android/hardware/power/CpuHeadroomParams.h>
 #include <aidl/android/hardware/power/GpuHeadroomParams.h>
 #include <aidl/android/os/CpuHeadroomParamsInternal.h>
@@ -23,6 +25,17 @@
 #include <android/system_health.h>
 #include <binder/IServiceManager.h>
 #include <binder/Status.h>
+#include <system_health_private.h>
+
+#include <list>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <utility>
+
+#include "android-base/thread_annotations.h"
+#include "utils/SystemClock.h"
 
 using namespace android;
 using namespace aidl::android::os;
@@ -55,9 +68,20 @@
     IHintManager::HintManagerClientData mClientData;
 };
 
+static std::shared_ptr<IHintManager>* gIHintManagerForTesting = nullptr;
+static std::shared_ptr<ASystemHealthManager> gSystemHealthManagerForTesting = nullptr;
+
 ASystemHealthManager* ASystemHealthManager::getInstance() {
     static std::once_flag creationFlag;
     static ASystemHealthManager* instance = nullptr;
+    if (gSystemHealthManagerForTesting) {
+        return gSystemHealthManagerForTesting.get();
+    }
+    if (gIHintManagerForTesting) {
+        gSystemHealthManagerForTesting =
+                std::shared_ptr<ASystemHealthManager>(create(*gIHintManagerForTesting));
+        return gSystemHealthManagerForTesting.get();
+    }
     std::call_once(creationFlag, []() { instance = create(nullptr); });
     return instance;
 }
@@ -121,7 +145,8 @@
         }
         return EPIPE;
     }
-    *outHeadroom = res->get<hal::CpuHeadroomResult::Tag::globalHeadroom>();
+    *outHeadroom = res ? res->get<hal::CpuHeadroomResult::Tag::globalHeadroom>()
+                       : std::numeric_limits<float>::quiet_NaN();
     return OK;
 }
 
@@ -155,37 +180,20 @@
         }
         return EPIPE;
     }
-    *outHeadroom = res->get<hal::GpuHeadroomResult::Tag::globalHeadroom>();
+    *outHeadroom = res ? res->get<hal::GpuHeadroomResult::Tag::globalHeadroom>()
+                       : std::numeric_limits<float>::quiet_NaN();
     return OK;
 }
 
 int ASystemHealthManager::getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) {
     if (!mClientData.supportInfo.headroom.isCpuSupported) return ENOTSUP;
-    int64_t minIntervalMillis = 0;
-    ::ndk::ScopedAStatus ret = mHintManager->getCpuHeadroomMinIntervalMillis(&minIntervalMillis);
-    if (!ret.isOk()) {
-        ALOGE("ASystemHealth_getCpuHeadroomMinIntervalMillis fails: %s", ret.getMessage());
-        if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
-            return ENOTSUP;
-        }
-        return EPIPE;
-    }
-    *outMinIntervalMillis = minIntervalMillis;
+    *outMinIntervalMillis = mClientData.supportInfo.headroom.cpuMinIntervalMillis;
     return OK;
 }
 
 int ASystemHealthManager::getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) {
     if (!mClientData.supportInfo.headroom.isGpuSupported) return ENOTSUP;
-    int64_t minIntervalMillis = 0;
-    ::ndk::ScopedAStatus ret = mHintManager->getGpuHeadroomMinIntervalMillis(&minIntervalMillis);
-    if (!ret.isOk()) {
-        ALOGE("ASystemHealth_getGpuHeadroomMinIntervalMillis fails: %s", ret.getMessage());
-        if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
-            return ENOTSUP;
-        }
-        return EPIPE;
-    }
-    *outMinIntervalMillis = minIntervalMillis;
+    *outMinIntervalMillis = mClientData.supportInfo.headroom.gpuMinIntervalMillis;
     return OK;
 }
 
@@ -298,7 +306,6 @@
                                 size_t tidsSize) {
     LOG_ALWAYS_FATAL_IF(tids == nullptr, "%s: tids should not be null", __FUNCTION__);
     params->tids.resize(tidsSize);
-    params->tids.clear();
     for (int i = 0; i < (int)tidsSize; ++i) {
         LOG_ALWAYS_FATAL_IF(tids[i] <= 0, "ACpuHeadroomParams_setTids: Invalid non-positive tid %d",
                             tids[i]);
@@ -355,3 +362,10 @@
 void AGpuHeadroomParams_destroy(AGpuHeadroomParams* _Nullable params) {
     delete params;
 }
+
+void ASystemHealth_setIHintManagerForTesting(void* iManager) {
+    if (iManager == nullptr) {
+        gSystemHealthManagerForTesting = nullptr;
+    }
+    gIHintManagerForTesting = static_cast<std::shared_ptr<IHintManager>*>(iManager);
+}
diff --git a/native/android/tests/system_health/Android.bp b/native/android/tests/system_health/Android.bp
new file mode 100644
index 0000000..30aeb77
--- /dev/null
+++ b/native/android/tests/system_health/Android.bp
@@ -0,0 +1,66 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+cc_test {
+    name: "NativeSystemHealthUnitTestCases",
+
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
+    srcs: ["NativeSystemHealthUnitTest.cpp"],
+
+    shared_libs: [
+        "libandroid",
+        "libbinder",
+        "libbinder_ndk",
+        "liblog",
+        "libpowermanager",
+        "libutils",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libgmock",
+        "libgtest",
+    ],
+    stl: "c++_shared",
+
+    test_suites: [
+        "device-tests",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    header_libs: [
+        "libandroid_headers_private",
+    ],
+}
diff --git a/native/android/tests/system_health/NativeSystemHealthUnitTest.cpp b/native/android/tests/system_health/NativeSystemHealthUnitTest.cpp
new file mode 100644
index 0000000..3f08fc6
--- /dev/null
+++ b/native/android/tests/system_health/NativeSystemHealthUnitTest.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NativeSystemHealthUnitTest"
+
+#include <aidl/android/os/IHintManager.h>
+#include <android/binder_manager.h>
+#include <android/binder_status.h>
+#include <android/system_health.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <system_health_private.h>
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+using namespace std::chrono_literals;
+namespace hal = aidl::android::hardware::power;
+using aidl::android::os::CpuHeadroomParamsInternal;
+using aidl::android::os::GpuHeadroomParamsInternal;
+using aidl::android::os::IHintManager;
+using aidl::android::os::IHintSession;
+using aidl::android::os::SessionCreationConfig;
+using ndk::ScopedAStatus;
+using ndk::SpAIBinder;
+
+using namespace android;
+using namespace testing;
+
+class MockIHintManager : public IHintManager {
+public:
+    MOCK_METHOD(ScopedAStatus, createHintSessionWithConfig,
+                (const SpAIBinder& token, hal::SessionTag tag,
+                 const SessionCreationConfig& creationConfig, hal::SessionConfig* config,
+                 IHintManager::SessionCreationReturn* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, setHintSessionThreads,
+                (const std::shared_ptr<IHintSession>& _, const ::std::vector<int32_t>& tids),
+                (override));
+    MOCK_METHOD(ScopedAStatus, getHintSessionThreadIds,
+                (const std::shared_ptr<IHintSession>& _, ::std::vector<int32_t>* tids), (override));
+    MOCK_METHOD(ScopedAStatus, getSessionChannel,
+                (const ::ndk::SpAIBinder& in_token,
+                 std::optional<hal::ChannelConfig>* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override));
+    MOCK_METHOD(ScopedAStatus, getCpuHeadroom,
+                (const CpuHeadroomParamsInternal& _,
+                 std::optional<hal::CpuHeadroomResult>* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, getCpuHeadroomMinIntervalMillis, (int64_t*), (override));
+    MOCK_METHOD(ScopedAStatus, getGpuHeadroom,
+                (const GpuHeadroomParamsInternal& _,
+                 std::optional<hal::GpuHeadroomResult>* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, getGpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, passSessionManagerBinder, (const SpAIBinder& sessionManager));
+    MOCK_METHOD(ScopedAStatus, registerClient,
+                (const std::shared_ptr<aidl::android::os::IHintManager::IHintManagerClient>& _,
+                 aidl::android::os::IHintManager::HintManagerClientData* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, getClientData,
+                (aidl::android::os::IHintManager::HintManagerClientData * _aidl_return),
+                (override));
+    MOCK_METHOD(SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
+};
+
+class NativeSystemHealthUnitTest : public Test {
+public:
+    void SetUp() override {
+        mMockIHintManager = ndk::SharedRefBase::make<NiceMock<MockIHintManager>>();
+        ASystemHealth_setIHintManagerForTesting(&mMockIHintManager);
+        ON_CALL(*mMockIHintManager, getClientData(_))
+                .WillByDefault(
+                        DoAll(SetArgPointee<0>(mClientData), [] { return ScopedAStatus::ok(); }));
+    }
+
+    void TearDown() override {
+        ASystemHealth_setIHintManagerForTesting(nullptr);
+    }
+
+    IHintManager::HintManagerClientData mClientData{
+            .powerHalVersion = 6,
+            .maxCpuHeadroomThreads = 10,
+            .supportInfo{.headroom{
+                    .isCpuSupported = true,
+                    .isGpuSupported = true,
+                    .cpuMinIntervalMillis = 999,
+                    .gpuMinIntervalMillis = 998,
+                    .cpuMinCalculationWindowMillis = 45,
+                    .cpuMaxCalculationWindowMillis = 9999,
+                    .gpuMinCalculationWindowMillis = 46,
+                    .gpuMaxCalculationWindowMillis = 9998,
+            }},
+    };
+
+    std::shared_ptr<NiceMock<MockIHintManager>> mMockIHintManager = nullptr;
+};
+
+TEST_F(NativeSystemHealthUnitTest, headroomParamsValueRange) {
+    int64_t minIntervalMillis = 0;
+    int minCalculationWindowMillis = 0;
+    int maxCalculationWindowMillis = 0;
+    ASSERT_EQ(OK, ASystemHealth_getCpuHeadroomMinIntervalMillis(&minIntervalMillis));
+    ASSERT_EQ(OK,
+              ASystemHealth_getCpuHeadroomCalculationWindowRange(&minCalculationWindowMillis,
+                                                                 &maxCalculationWindowMillis));
+    ASSERT_EQ(minIntervalMillis, mClientData.supportInfo.headroom.cpuMinIntervalMillis);
+    ASSERT_EQ(minCalculationWindowMillis,
+              mClientData.supportInfo.headroom.cpuMinCalculationWindowMillis);
+    ASSERT_EQ(maxCalculationWindowMillis,
+              mClientData.supportInfo.headroom.cpuMaxCalculationWindowMillis);
+
+    ASSERT_EQ(OK, ASystemHealth_getGpuHeadroomMinIntervalMillis(&minIntervalMillis));
+    ASSERT_EQ(OK,
+              ASystemHealth_getGpuHeadroomCalculationWindowRange(&minCalculationWindowMillis,
+                                                                 &maxCalculationWindowMillis));
+    ASSERT_EQ(minIntervalMillis, mClientData.supportInfo.headroom.gpuMinIntervalMillis);
+    ASSERT_EQ(minCalculationWindowMillis,
+              mClientData.supportInfo.headroom.gpuMinCalculationWindowMillis);
+    ASSERT_EQ(maxCalculationWindowMillis,
+              mClientData.supportInfo.headroom.gpuMaxCalculationWindowMillis);
+}
+
+TEST_F(NativeSystemHealthUnitTest, getCpuHeadroom) {
+    CpuHeadroomParamsInternal internalParams1;
+    ACpuHeadroomParams* params2 = ACpuHeadroomParams_create();
+    ACpuHeadroomParams_setCalculationWindowMillis(params2, 200);
+    CpuHeadroomParamsInternal internalParams2;
+    internalParams2.calculationWindowMillis = 200;
+    ACpuHeadroomParams* params3 = ACpuHeadroomParams_create();
+    ACpuHeadroomParams_setCalculationType(params3, ACPU_HEADROOM_CALCULATION_TYPE_AVERAGE);
+    CpuHeadroomParamsInternal internalParams3;
+    internalParams3.calculationType = hal::CpuHeadroomParams::CalculationType::AVERAGE;
+    ACpuHeadroomParams* params4 = ACpuHeadroomParams_create();
+    int tids[3] = {1, 2, 3};
+    ACpuHeadroomParams_setTids(params4, tids, 3);
+    CpuHeadroomParamsInternal internalParams4;
+    internalParams4.tids = {1, 2, 3};
+
+    EXPECT_CALL(*mMockIHintManager, getCpuHeadroom(internalParams1, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(hal::CpuHeadroomResult::make<
+                                             hal::CpuHeadroomResult::globalHeadroom>(1.0f)),
+                            [] { return ScopedAStatus::ok(); }));
+    EXPECT_CALL(*mMockIHintManager, getCpuHeadroom(internalParams2, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(hal::CpuHeadroomResult::make<
+                                             hal::CpuHeadroomResult::globalHeadroom>(2.0f)),
+                            [] { return ScopedAStatus::ok(); }));
+    EXPECT_CALL(*mMockIHintManager, getCpuHeadroom(internalParams3, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(std::nullopt), [] { return ScopedAStatus::ok(); }));
+    EXPECT_CALL(*mMockIHintManager, getCpuHeadroom(internalParams4, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(hal::CpuHeadroomResult::make<
+                                             hal::CpuHeadroomResult::globalHeadroom>(4.0f)),
+                            [] { return ScopedAStatus::ok(); }));
+
+    float headroom1 = 0.0f;
+    float headroom2 = 0.0f;
+    float headroom3 = 0.0f;
+    float headroom4 = 0.0f;
+    ASSERT_EQ(OK, ASystemHealth_getCpuHeadroom(nullptr, &headroom1));
+    ASSERT_EQ(OK, ASystemHealth_getCpuHeadroom(params2, &headroom2));
+    ASSERT_EQ(OK, ASystemHealth_getCpuHeadroom(params3, &headroom3));
+    ASSERT_EQ(OK, ASystemHealth_getCpuHeadroom(params4, &headroom4));
+    ASSERT_EQ(1.0f, headroom1);
+    ASSERT_EQ(2.0f, headroom2);
+    ASSERT_TRUE(isnan(headroom3));
+    ASSERT_EQ(4.0f, headroom4);
+
+    ACpuHeadroomParams_destroy(params2);
+    ACpuHeadroomParams_destroy(params3);
+    ACpuHeadroomParams_destroy(params4);
+}
+
+TEST_F(NativeSystemHealthUnitTest, getGpuHeadroom) {
+    GpuHeadroomParamsInternal internalParams1;
+    AGpuHeadroomParams* params2 = AGpuHeadroomParams_create();
+    AGpuHeadroomParams_setCalculationWindowMillis(params2, 200);
+    GpuHeadroomParamsInternal internalParams2;
+    internalParams2.calculationWindowMillis = 200;
+    AGpuHeadroomParams* params3 = AGpuHeadroomParams_create();
+    AGpuHeadroomParams_setCalculationType(params3, AGPU_HEADROOM_CALCULATION_TYPE_AVERAGE);
+    GpuHeadroomParamsInternal internalParams3;
+    internalParams3.calculationType = hal::GpuHeadroomParams::CalculationType::AVERAGE;
+
+    EXPECT_CALL(*mMockIHintManager, getGpuHeadroom(internalParams1, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(hal::GpuHeadroomResult::make<
+                                             hal::GpuHeadroomResult::globalHeadroom>(1.0f)),
+                            [] { return ScopedAStatus::ok(); }));
+    EXPECT_CALL(*mMockIHintManager, getGpuHeadroom(internalParams2, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(hal::GpuHeadroomResult::make<
+                                             hal::GpuHeadroomResult::globalHeadroom>(2.0f)),
+                            [] { return ScopedAStatus::ok(); }));
+    EXPECT_CALL(*mMockIHintManager, getGpuHeadroom(internalParams3, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(std::nullopt), [] { return ScopedAStatus::ok(); }));
+
+    float headroom1 = 0.0f;
+    float headroom2 = 0.0f;
+    float headroom3 = 0.0f;
+    ASSERT_EQ(OK, ASystemHealth_getGpuHeadroom(nullptr, &headroom1));
+    ASSERT_EQ(OK, ASystemHealth_getGpuHeadroom(params2, &headroom2));
+    ASSERT_EQ(OK, ASystemHealth_getGpuHeadroom(params3, &headroom3));
+    ASSERT_EQ(1.0f, headroom1);
+    ASSERT_EQ(2.0f, headroom2);
+    ASSERT_TRUE(isnan(headroom3));
+
+    AGpuHeadroomParams_destroy(params2);
+    AGpuHeadroomParams_destroy(params3);
+}