Add NDK API AThermal_getThermalHeadroomThresholds

Bug: b/288119641
Test: atest NativeThermalUnitTestCases
Change-Id: I2ba820da74b290a25ff1edf8d278b9200dfaf950
diff --git a/native/android/TEST_MAPPING b/native/android/TEST_MAPPING
index fd394fc..7c71098 100644
--- a/native/android/TEST_MAPPING
+++ b/native/android/TEST_MAPPING
@@ -22,5 +22,15 @@
        ],
        "file_patterns": ["performance_hint.cpp"]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsThermalTestCases",
+       "file_patterns": ["thermal.cpp"]
+    },
+    {
+      "name": "NativeThermalUnitTestCases",
+       "file_patterns": ["thermal.cpp"]
+    }
   ]
 }
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b0af09c..fea6c5f 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -327,6 +327,7 @@
     AThermal_registerThermalStatusListener; # introduced=30
     AThermal_unregisterThermalStatusListener; # introduced=30
     AThermal_getThermalHeadroom; # introduced=31
+    AThermal_getThermalHeadroomThresholds; # introduced=VanillaIceCream
     APerformanceHint_getManager; # introduced=Tiramisu
     APerformanceHint_createSession; # introduced=Tiramisu
     APerformanceHint_getPreferredUpdateRateNanos; # introduced=Tiramisu
@@ -341,6 +342,7 @@
 
 LIBANDROID_PLATFORM {
   global:
+    AThermal_setIThermalServiceForTesting;
     APerformanceHint_setIHintManagerForTesting;
     APerformanceHint_sendHint;
     APerformanceHint_getThreadIds;
diff --git a/native/android/tests/thermal/Android.bp b/native/android/tests/thermal/Android.bp
new file mode 100644
index 0000000..8540d83
--- /dev/null
+++ b/native/android/tests/thermal/Android.bp
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 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: "NativeThermalUnitTestCases",
+
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
+    srcs: ["NativeThermalUnitTest.cpp"],
+
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libbinder",
+        "libpowermanager",
+        "libutils",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libgmock",
+        "libgtest",
+    ],
+    stl: "c++_shared",
+
+    test_suites: [
+        "device-tests",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+
+    header_libs: [
+        "libandroid_headers_private",
+    ],
+}
diff --git a/native/android/tests/thermal/NativeThermalUnitTest.cpp b/native/android/tests/thermal/NativeThermalUnitTest.cpp
new file mode 100644
index 0000000..6d6861a
--- /dev/null
+++ b/native/android/tests/thermal/NativeThermalUnitTest.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2023 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 "NativeThermalUnitTest"
+
+#include <android/os/IThermalService.h>
+#include <android/thermal.h>
+#include <binder/IBinder.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <thermal_private.h>
+
+using android::binder::Status;
+
+using namespace testing;
+using namespace android;
+using namespace android::os;
+
+class MockIThermalService : public IThermalService {
+public:
+    MOCK_METHOD(Status, registerThermalEventListener,
+                (const ::android::sp<::android::os::IThermalEventListener>& listener,
+                 bool* _aidl_return),
+                (override));
+    MOCK_METHOD(Status, registerThermalEventListenerWithType,
+                (const ::android::sp<::android::os::IThermalEventListener>& listener, int32_t type,
+                 bool* _aidl_return),
+                (override));
+    MOCK_METHOD(Status, unregisterThermalEventListener,
+                (const ::android::sp<::android::os::IThermalEventListener>& listener,
+                 bool* _aidl_return),
+                (override));
+    MOCK_METHOD(Status, getCurrentTemperatures,
+                (::std::vector<::android::os::Temperature> * _aidl_return), (override));
+    MOCK_METHOD(Status, getCurrentTemperaturesWithType,
+                (int32_t type, ::std::vector<::android::os::Temperature>* _aidl_return),
+                (override));
+    MOCK_METHOD(Status, registerThermalStatusListener,
+                (const ::android::sp<::android::os::IThermalStatusListener>& listener,
+                 bool* _aidl_return),
+                (override));
+    MOCK_METHOD(Status, unregisterThermalStatusListener,
+                (const ::android::sp<::android::os::IThermalStatusListener>& listener,
+                 bool* _aidl_return),
+                (override));
+    MOCK_METHOD(Status, getCurrentThermalStatus, (int32_t * _aidl_return), (override));
+    MOCK_METHOD(Status, getCurrentCoolingDevices,
+                (::std::vector<::android::os::CoolingDevice> * _aidl_return), (override));
+    MOCK_METHOD(Status, getCurrentCoolingDevicesWithType,
+                (int32_t type, ::std::vector<::android::os::CoolingDevice>* _aidl_return),
+                (override));
+    MOCK_METHOD(Status, getThermalHeadroom, (int32_t forecastSeconds, float* _aidl_return),
+                (override));
+    MOCK_METHOD(Status, getThermalHeadroomThresholds, (::std::vector<float> * _aidl_return),
+                (override));
+    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+class NativeThermalUnitTest : public Test {
+public:
+    void SetUp() override {
+        mMockIThermalService = new StrictMock<MockIThermalService>();
+        AThermal_setIThermalServiceForTesting(mMockIThermalService);
+        mThermalManager = AThermal_acquireManager();
+    }
+
+    void TearDown() override {
+        AThermal_setIThermalServiceForTesting(nullptr);
+        AThermal_releaseManager(mThermalManager);
+    }
+
+    StrictMock<MockIThermalService>* mMockIThermalService = nullptr;
+    AThermalManager* mThermalManager = nullptr;
+};
+
+static void checkThermalHeadroomThresholds(const std::vector<float>& expected,
+                                           const AThermalHeadroomThreshold* thresholds,
+                                           size_t size) {
+    if (thresholds == nullptr) {
+        FAIL() << "Unexpected null thresholds pointer";
+    }
+    for (int i = 0; i < (int)size; i++) {
+        auto t = thresholds[i];
+        ASSERT_EQ(i, t.thermalStatus) << "threshold " << i << " should have status " << i;
+        ASSERT_EQ(expected[i], t.headroom)
+                << "threshold " << i << " should have headroom " << expected[i];
+    }
+}
+
+TEST_F(NativeThermalUnitTest, TestGetThermalHeadroomThresholds) {
+    std::vector<float> expected = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+    EXPECT_CALL(*mMockIThermalService, getThermalHeadroomThresholds(_))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<0>(expected), Return(Status())));
+    const AThermalHeadroomThreshold* thresholds1 = nullptr;
+    size_t size1;
+    ASSERT_EQ(OK, AThermal_getThermalHeadroomThresholds(mThermalManager, &thresholds1, &size1));
+    checkThermalHeadroomThresholds(expected, thresholds1, size1);
+    // following calls should be cached
+    EXPECT_CALL(*mMockIThermalService, getThermalHeadroomThresholds(_)).Times(0);
+
+    const AThermalHeadroomThreshold* thresholds2 = nullptr;
+    size_t size2;
+    ASSERT_EQ(OK, AThermal_getThermalHeadroomThresholds(mThermalManager, &thresholds2, &size2));
+    checkThermalHeadroomThresholds(expected, thresholds2, size2);
+}
+
+TEST_F(NativeThermalUnitTest, TestGetThermalHeadroomThresholdsFailedWithServerError) {
+    const AThermalHeadroomThreshold* thresholds = nullptr;
+    size_t size;
+    EXPECT_CALL(*mMockIThermalService, getThermalHeadroomThresholds(_))
+            .Times(Exactly(1))
+            .WillOnce(Return(
+                    Status::fromExceptionCode(binder::Status::Exception::EX_ILLEGAL_ARGUMENT)));
+    ASSERT_EQ(EPIPE, AThermal_getThermalHeadroomThresholds(mThermalManager, &thresholds, &size));
+    ASSERT_EQ(nullptr, thresholds);
+}
+
+TEST_F(NativeThermalUnitTest, TestGetThermalHeadroomThresholdsFailedWithFeatureDisabled) {
+    const AThermalHeadroomThreshold* thresholds = nullptr;
+    size_t size;
+    EXPECT_CALL(*mMockIThermalService, getThermalHeadroomThresholds(_))
+            .Times(Exactly(1))
+            .WillOnce(Return(Status::fromExceptionCode(
+                    binder::Status::Exception::EX_UNSUPPORTED_OPERATION)));
+    ASSERT_EQ(ENOSYS, AThermal_getThermalHeadroomThresholds(mThermalManager, &thresholds, &size));
+    ASSERT_EQ(nullptr, thresholds);
+}
+
+TEST_F(NativeThermalUnitTest, TestGetThermalHeadroomThresholdsFailedWithNullPtr) {
+    const AThermalHeadroomThreshold* thresholds = nullptr;
+    size_t size;
+    size_t* nullSize = nullptr;
+    ASSERT_EQ(EINVAL,
+              AThermal_getThermalHeadroomThresholds(mThermalManager, &thresholds, nullSize));
+    ASSERT_EQ(nullptr, thresholds);
+    ASSERT_EQ(EINVAL, AThermal_getThermalHeadroomThresholds(mThermalManager, nullptr, &size));
+}
+
+TEST_F(NativeThermalUnitTest, TestGetThermalHeadroomThresholdsFailedWithNonEmptyPtr) {
+    const AThermalHeadroomThreshold* initialized = new AThermalHeadroomThreshold[1];
+    size_t size;
+    ASSERT_EQ(EINVAL, AThermal_getThermalHeadroomThresholds(mThermalManager, &initialized, &size));
+    delete[] initialized;
+}
diff --git a/native/android/tests/thermal/OWNERS b/native/android/tests/thermal/OWNERS
new file mode 100644
index 0000000..e3bbee92
--- /dev/null
+++ b/native/android/tests/thermal/OWNERS
@@ -0,0 +1 @@
+include /ADPF_OWNERS
diff --git a/native/android/thermal.cpp b/native/android/thermal.cpp
index 1f6ef47..b43f2f16 100644
--- a/native/android/thermal.cpp
+++ b/native/android/thermal.cpp
@@ -16,27 +16,32 @@
 
 #define LOG_TAG "thermal"
 
-#include <cerrno>
-#include <thread>
-#include <limits>
-
-#include <android/thermal.h>
+#include <android-base/thread_annotations.h>
 #include <android/os/BnThermalStatusListener.h>
 #include <android/os/IThermalService.h>
+#include <android/thermal.h>
 #include <binder/IServiceManager.h>
+#include <thermal_private.h>
 #include <utils/Log.h>
 
+#include <cerrno>
+#include <limits>
+#include <thread>
+
 using android::sp;
 
 using namespace android;
 using namespace android::os;
 
 struct ThermalServiceListener : public BnThermalStatusListener {
-    public:
-        virtual binder::Status onStatusChange(int32_t status) override;
-        ThermalServiceListener(AThermalManager *manager) {mMgr = manager;}
-    private:
-        AThermalManager *mMgr;
+public:
+    virtual binder::Status onStatusChange(int32_t status) override;
+    ThermalServiceListener(AThermalManager *manager) {
+        mMgr = manager;
+    }
+
+private:
+    AThermalManager *mMgr;
 };
 
 struct ListenerCallback {
@@ -44,22 +49,29 @@
     void* data;
 };
 
+static IThermalService *gIThermalServiceForTesting = nullptr;
+
 struct AThermalManager {
-   public:
-        static AThermalManager* createAThermalManager();
-        AThermalManager() = delete;
-        ~AThermalManager();
-        status_t notifyStateChange(int32_t status);
-        status_t getCurrentThermalStatus(int32_t *status);
-        status_t addListener(AThermal_StatusCallback, void *data);
-        status_t removeListener(AThermal_StatusCallback, void *data);
-        status_t getThermalHeadroom(int32_t forecastSeconds, float *result);
-   private:
-       AThermalManager(sp<IThermalService> service);
-       sp<IThermalService> mThermalSvc;
-       sp<ThermalServiceListener> mServiceListener;
-       std::vector<ListenerCallback> mListeners;
-       std::mutex mMutex;
+public:
+    static AThermalManager *createAThermalManager();
+    AThermalManager() = delete;
+    ~AThermalManager();
+    status_t notifyStateChange(int32_t status);
+    status_t getCurrentThermalStatus(int32_t *status);
+    status_t addListener(AThermal_StatusCallback, void *data);
+    status_t removeListener(AThermal_StatusCallback, void *data);
+    status_t getThermalHeadroom(int32_t forecastSeconds, float *result);
+    status_t getThermalHeadroomThresholds(const AThermalHeadroomThreshold **, size_t *size);
+
+private:
+    AThermalManager(sp<IThermalService> service);
+    sp<IThermalService> mThermalSvc;
+    std::mutex mListenerMutex;
+    sp<ThermalServiceListener> mServiceListener GUARDED_BY(mListenerMutex);
+    std::vector<ListenerCallback> mListeners GUARDED_BY(mListenerMutex);
+    std::mutex mThresholdsMutex;
+    const AThermalHeadroomThreshold *mThresholds = nullptr; // GUARDED_BY(mThresholdsMutex)
+    size_t mThresholdsCount GUARDED_BY(mThresholdsMutex);
 };
 
 binder::Status ThermalServiceListener::onStatusChange(int32_t status) {
@@ -70,6 +82,9 @@
 }
 
 AThermalManager* AThermalManager::createAThermalManager() {
+    if (gIThermalServiceForTesting) {
+        return new AThermalManager(gIThermalServiceForTesting);
+    }
     sp<IBinder> binder =
             defaultServiceManager()->checkService(String16("thermalservice"));
 
@@ -81,12 +96,10 @@
 }
 
 AThermalManager::AThermalManager(sp<IThermalService> service)
-    : mThermalSvc(service),
-      mServiceListener(nullptr) {
-}
+      : mThermalSvc(std::move(service)), mServiceListener(nullptr) {}
 
 AThermalManager::~AThermalManager() {
-    std::unique_lock<std::mutex> lock(mMutex);
+    std::unique_lock<std::mutex> listenerLock(mListenerMutex);
 
     mListeners.clear();
     if (mServiceListener != nullptr) {
@@ -94,10 +107,13 @@
         mThermalSvc->unregisterThermalStatusListener(mServiceListener, &success);
         mServiceListener = nullptr;
     }
+    listenerLock.unlock();
+    std::unique_lock<std::mutex> lock(mThresholdsMutex);
+    delete[] mThresholds;
 }
 
 status_t AThermalManager::notifyStateChange(int32_t status) {
-    std::unique_lock<std::mutex> lock(mMutex);
+    std::unique_lock<std::mutex> lock(mListenerMutex);
     AThermalStatus thermalStatus = static_cast<AThermalStatus>(status);
 
     for (auto listener : mListeners) {
@@ -107,7 +123,7 @@
 }
 
 status_t AThermalManager::addListener(AThermal_StatusCallback callback, void *data) {
-    std::unique_lock<std::mutex> lock(mMutex);
+    std::unique_lock<std::mutex> lock(mListenerMutex);
 
     if (callback == nullptr) {
         // Callback can not be nullptr
@@ -141,7 +157,7 @@
 }
 
 status_t AThermalManager::removeListener(AThermal_StatusCallback callback, void *data) {
-    std::unique_lock<std::mutex> lock(mMutex);
+    std::unique_lock<std::mutex> lock(mListenerMutex);
 
     auto it = std::remove_if(mListeners.begin(),
                              mListeners.end(),
@@ -198,6 +214,32 @@
     return OK;
 }
 
+status_t AThermalManager::getThermalHeadroomThresholds(const AThermalHeadroomThreshold **result,
+                                                       size_t *size) {
+    std::unique_lock<std::mutex> lock(mThresholdsMutex);
+    if (mThresholds == nullptr) {
+        auto thresholds = std::make_unique<std::vector<float>>();
+        binder::Status ret = mThermalSvc->getThermalHeadroomThresholds(thresholds.get());
+        if (!ret.isOk()) {
+            if (ret.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+                // feature is not enabled
+                return ENOSYS;
+            }
+            return EPIPE;
+        }
+        mThresholdsCount = thresholds->size();
+        auto t = new AThermalHeadroomThreshold[mThresholdsCount];
+        for (int i = 0; i < (int)mThresholdsCount; i++) {
+            t[i].headroom = (*thresholds)[i];
+            t[i].thermalStatus = static_cast<AThermalStatus>(i);
+        }
+        mThresholds = t;
+    }
+    *size = mThresholdsCount;
+    *result = mThresholds;
+    return OK;
+}
+
 /**
   * Acquire an instance of the thermal manager. This must be freed using
   * {@link AThermal_releaseManager}.
@@ -291,14 +333,24 @@
  *  	   threshold. Returns NaN if the device does not support this functionality or if
  * 	       this function is called significantly faster than once per second.
  */
-float AThermal_getThermalHeadroom(AThermalManager *manager,
-        int forecastSeconds) {
+float AThermal_getThermalHeadroom(AThermalManager *manager, int forecastSeconds) {
     float result = 0.0f;
     status_t ret = manager->getThermalHeadroom(forecastSeconds, &result);
-
     if (ret != OK) {
         result = std::numeric_limits<float>::quiet_NaN();
     }
-
     return result;
 }
+
+int AThermal_getThermalHeadroomThresholds(AThermalManager *manager,
+                                          const AThermalHeadroomThreshold **outThresholds,
+                                          size_t *size) {
+    if (outThresholds == nullptr || *outThresholds != nullptr || size == nullptr) {
+        return EINVAL;
+    }
+    return manager->getThermalHeadroomThresholds(outThresholds, size);
+}
+
+void AThermal_setIThermalServiceForTesting(void *iThermalService) {
+    gIThermalServiceForTesting = static_cast<IThermalService *>(iThermalService);
+}