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);
+}