1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "NativeThermalTest"
19
20 #include <condition_variable>
21 #include <jni.h>
22 #include <mutex>
23 #include <optional>
24 #include <thread>
25 #include <inttypes.h>
26 #include <time.h>
27 #include <unistd.h>
28 #include <math.h>
29 #include <vector>
30
31 #include <android/thermal.h>
32 #include <android-base/stringprintf.h>
33 #include <android-base/strings.h>
34 #include <android-base/thread_annotations.h>
35 #include <log/log.h>
36 #include <sys/stat.h>
37 #include <utils/Errors.h>
38
39 using namespace android;
40 using namespace std::chrono_literals;
41 using android::base::StringPrintf;
42
43 struct AThermalTestContext {
44 AThermalManager *mThermalMgr;
45 std::mutex mMutex;
46 std::condition_variable mCv;
47 std::vector<AThermalStatus> mListenerStatus GUARDED_BY(mMutex);
48 };
49
50 static jclass gNativeThermalTest_class;
51 static jmethodID gNativeThermalTest_thermalOverrideMethodID;
52
onStatusChange(void * data,AThermalStatus status)53 void onStatusChange(void *data, AThermalStatus status) {
54 AThermalTestContext *ctx = static_cast<AThermalTestContext *>(data);
55 if (ctx == nullptr) {
56 return;
57 } else {
58 std::lock_guard<std::mutex> guard(ctx->mMutex);
59 ctx->mListenerStatus.push_back(status);
60 ctx->mCv.notify_all();
61 }
62 }
63
setThermalStatusOverride(JNIEnv * env,jobject obj,int32_t level)64 static inline void setThermalStatusOverride(JNIEnv* env, jobject obj, int32_t level) {
65 env->CallVoidMethod(obj, gNativeThermalTest_thermalOverrideMethodID, level);
66 }
67
returnJString(JNIEnv * env,std::optional<std::string> result)68 static inline jstring returnJString(JNIEnv *env, std::optional<std::string> result) {
69 if (result.has_value()) {
70 return env->NewStringUTF(result.value().c_str());
71 } else {
72 return env->NewStringUTF("");
73 }
74 }
75
testGetCurrentThermalStatus(JNIEnv * env,jobject obj,int32_t level)76 static std::optional<std::string> testGetCurrentThermalStatus(
77 JNIEnv *env, jobject obj, int32_t level) {
78 AThermalTestContext ctx;
79
80 ctx.mThermalMgr = AThermal_acquireManager();
81 if (ctx.mThermalMgr == nullptr) {
82 return "AThermal_acquireManager failed";
83 }
84
85 setThermalStatusOverride(env, obj, level);
86 AThermalStatus thermalStatus = AThermal_getCurrentThermalStatus(ctx.mThermalMgr);
87 if (thermalStatus == ATHERMAL_STATUS_ERROR) {
88 return "getCurrentThermalStatus returns ATHERMAL_STATUS_ERROR";
89 }
90 // Verify the current thermal status is same as override
91 if (thermalStatus != static_cast<AThermalStatus>(level)) {
92 return StringPrintf("getCurrentThermalStatus %" PRId32 " != override %" PRId32 ".",
93 thermalStatus, level);
94 }
95
96 AThermal_releaseManager(ctx.mThermalMgr);
97 return std::nullopt;
98 }
99
nativeTestGetCurrentThermalStatus(JNIEnv * env,jobject obj,jint level)100 static jstring nativeTestGetCurrentThermalStatus(JNIEnv *env, jobject obj, jint level) {
101 return returnJString(env, testGetCurrentThermalStatus(env, obj, static_cast<int32_t>(level)));
102 }
103
testRegisterThermalStatusListener(JNIEnv * env,jobject obj)104 static std::optional<std::string> testRegisterThermalStatusListener(JNIEnv *env, jobject obj) {
105 AThermalTestContext ctx;
106 std::unique_lock<std::mutex> lock(ctx.mMutex);
107
108 ctx.mThermalMgr = AThermal_acquireManager();
109 if (ctx.mThermalMgr == nullptr) {
110 return "AThermal_acquireManager failed";
111 }
112
113 // Register a listener with valid callback
114 int ret = AThermal_registerThermalStatusListener(ctx.mThermalMgr, onStatusChange, &ctx);
115 if (ret != 0) {
116 return StringPrintf("AThermal_registerThermalStatusListener failed: %s",
117 strerror(ret));
118 }
119
120 // Expect the callback after registration
121 if (ctx.mCv.wait_for(lock, 1s) == std::cv_status::timeout) {
122 return "Listener callback should be called after registration";
123 }
124
125 // Verify the current thermal status is same as listener callback
126 auto thermalStatus = AThermal_getCurrentThermalStatus(ctx.mThermalMgr);
127 auto listenerStatus = ctx.mListenerStatus.back();
128 if (thermalStatus != listenerStatus) {
129 return StringPrintf("thermalStatus %" PRId32 " != Listener status %" PRId32 ".",
130 thermalStatus, listenerStatus);
131 }
132
133 // Change override level and verify the listener callback
134 for (int32_t level = ATHERMAL_STATUS_LIGHT; level <= ATHERMAL_STATUS_SHUTDOWN; level++) {
135 setThermalStatusOverride(env, obj, level);
136 if (ctx.mCv.wait_for(lock, 1s) == std::cv_status::timeout) {
137 return StringPrintf("Listener callback timeout at level %" PRId32, level);
138 }
139 auto overrideStatus = static_cast<AThermalStatus>(level);
140 auto listenerStatus = ctx.mListenerStatus.back();
141 if (listenerStatus != overrideStatus) {
142 return StringPrintf("Listener thermalStatus%" PRId32 " != override %" PRId32 ".",
143 listenerStatus, overrideStatus);
144 }
145 }
146
147 // Unregister listener
148 ret = AThermal_unregisterThermalStatusListener(ctx.mThermalMgr, onStatusChange, &ctx);
149 if (ret != 0) {
150 return StringPrintf("AThermal_unregisterThermalStatusListener failed: %s",
151 strerror(ret));
152 }
153
154 AThermal_releaseManager(ctx.mThermalMgr);
155 return std::nullopt;
156 }
157
nativeTestRegisterThermalStatusListener(JNIEnv * env,jobject obj)158 static jstring nativeTestRegisterThermalStatusListener(JNIEnv *env, jobject obj) {
159 return returnJString(env, testRegisterThermalStatusListener(env, obj));
160 }
161
testThermalStatusRegisterNullListener()162 static std::optional<std::string> testThermalStatusRegisterNullListener() {
163 AThermalTestContext ctx;
164
165 ctx.mThermalMgr = AThermal_acquireManager();
166 if (ctx.mThermalMgr == nullptr) {
167 return StringPrintf("AThermal_acquireManager failed");
168 }
169
170 // Register a listener with null callback
171 int ret = AThermal_registerThermalStatusListener(ctx.mThermalMgr, nullptr, &ctx);
172 if (ret != EINVAL) {
173 return "AThermal_registerThermalStatusListener should fail with null callback";
174 }
175
176 // Register a listener with null data
177 ret = AThermal_registerThermalStatusListener(ctx.mThermalMgr, onStatusChange, &ctx);
178 if (ret != 0) {
179 return StringPrintf("AThermal_registerThermalStatusListener failed: %s",
180 strerror(ret));
181 }
182
183 // Unregister listener with null callback and null data
184 ret = AThermal_unregisterThermalStatusListener(ctx.mThermalMgr, nullptr, nullptr);
185 if (ret != EINVAL) {
186 return "AThermal_unregisterThermalStatusListener should fail with null listener";
187 }
188
189 AThermal_releaseManager(ctx.mThermalMgr);
190 return std::nullopt;
191 }
192
nativeTestThermalStatusRegisterNullListener(JNIEnv * env,jobject)193 static jstring nativeTestThermalStatusRegisterNullListener(JNIEnv *env, jobject) {
194 return returnJString(env, testThermalStatusRegisterNullListener());
195 }
196
testThermalStatusListenerDoubleRegistration(JNIEnv * env,jobject obj)197 static std::optional<std::string> testThermalStatusListenerDoubleRegistration
198 (JNIEnv *env, jobject obj) {
199 AThermalTestContext ctx;
200 std::unique_lock<std::mutex> lock(ctx.mMutex);
201
202 ctx.mThermalMgr = AThermal_acquireManager();
203 if (ctx.mThermalMgr == nullptr) {
204 return "AThermal_acquireManager failed";
205 }
206
207 // Register a listener with valid callback
208 int ret = AThermal_registerThermalStatusListener(ctx.mThermalMgr, onStatusChange, &ctx);
209 if (ret != 0) {
210 return StringPrintf("AThermal_registerThermalStatusListener failed: %s",
211 strerror(ret));
212 }
213
214 // Register the listener again with same callback and data
215 ret = AThermal_registerThermalStatusListener(ctx.mThermalMgr, onStatusChange, &ctx);
216 if (ret != EINVAL) {
217 return "Register should fail as listener already registered";
218 }
219
220 // Register a listener with same callback but null data
221 ret = AThermal_registerThermalStatusListener(ctx.mThermalMgr, onStatusChange, nullptr);
222 if (ret != 0) {
223 return StringPrintf("Register listener with null data failed: %s", strerror(ret));
224 }
225
226 // Expect listener callback
227 if (ctx.mCv.wait_for(lock, 1s) == std::cv_status::timeout) {
228 return "Thermal listener callback timeout";
229 }
230
231 // Unregister listener
232 ret = AThermal_unregisterThermalStatusListener(ctx.mThermalMgr, onStatusChange, &ctx);
233 if (ret != 0) {
234 return StringPrintf("AThermal_unregisterThermalStatusListener failed: %s",
235 strerror(ret));
236 }
237
238 for (int32_t level = ATHERMAL_STATUS_LIGHT; level <= ATHERMAL_STATUS_SHUTDOWN; level++) {
239 setThermalStatusOverride(env, obj, level);
240 // Expect no listener callback
241 if (ctx.mCv.wait_for(lock, 1s) != std::cv_status::timeout) {
242 return "Thermal listener got callback after unregister.";
243 }
244 }
245
246 // Unregister listener already unregistered
247 ret = AThermal_unregisterThermalStatusListener(ctx.mThermalMgr, onStatusChange, &ctx);
248 if (ret != EINVAL) {
249 return "Unregister should fail with listener already unregistered";
250 }
251
252 AThermal_releaseManager(ctx.mThermalMgr);
253 return std::nullopt;
254 }
255
nativeTestThermalStatusListenerDoubleRegistration(JNIEnv * env,jobject obj)256 static jstring nativeTestThermalStatusListenerDoubleRegistration(JNIEnv *env, jobject obj) {
257 return returnJString(env, testThermalStatusListenerDoubleRegistration(env, obj));
258 }
259
testGetThermalHeadroom(JNIEnv *,jobject)260 static std::optional<std::string> testGetThermalHeadroom(JNIEnv *, jobject) {
261 AThermalTestContext ctx;
262 std::unique_lock<std::mutex> lock(ctx.mMutex);
263
264 ctx.mThermalMgr = AThermal_acquireManager();
265 if (ctx.mThermalMgr == nullptr) {
266 return "AThermal_acquireManager failed";
267 }
268
269 // Fairly light touch test only. More in-depth testing of the underlying
270 // Thermal API functionality is done against the equivalent Java API.
271
272 float headroom = AThermal_getThermalHeadroom(ctx.mThermalMgr, 0);
273 if (isnan(headroom)) {
274 // If the device doesn't support thermal headroom, return early.
275 // This is not a failure.
276 return std::nullopt;
277 }
278
279 if (headroom < 0.0f) {
280 return StringPrintf("Expected non-negative headroom but got %2.2f",
281 headroom);
282 }
283 if (headroom >= 10.0f) {
284 return StringPrintf("Expected reasonably small (<10) headroom but got %2.2f", headroom);
285 }
286
287 AThermal_releaseManager(ctx.mThermalMgr);
288 return std::nullopt;
289 }
290
nativeTestGetThermalHeadroom(JNIEnv * env,jobject obj)291 static jstring nativeTestGetThermalHeadroom(JNIEnv *env, jobject obj) {
292 return returnJString(env, testGetThermalHeadroom(env, obj));
293 }
294
JNI_OnLoad(JavaVM * vm,void *)295 extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
296 JNIEnv* env;
297 const JNINativeMethod methodTable[] = {
298 {"nativeTestGetCurrentThermalStatus", "(I)Ljava/lang/String;",
299 (void*)nativeTestGetCurrentThermalStatus},
300 {"nativeTestRegisterThermalStatusListener", "()Ljava/lang/String;",
301 (void*)nativeTestRegisterThermalStatusListener},
302 {"nativeTestThermalStatusRegisterNullListener", "()Ljava/lang/String;",
303 (void*)nativeTestThermalStatusRegisterNullListener},
304 {"nativeTestThermalStatusListenerDoubleRegistration", "()Ljava/lang/String;",
305 (void*)nativeTestThermalStatusListenerDoubleRegistration},
306 {"nativeTestGetThermalHeadroom", "()Ljava/lang/String;",
307 (void*)nativeTestGetThermalHeadroom},
308 };
309 if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
310 return JNI_ERR;
311 }
312 gNativeThermalTest_class = env->FindClass("android/thermal/cts/NativeThermalTest");
313 gNativeThermalTest_thermalOverrideMethodID =
314 env->GetMethodID(gNativeThermalTest_class, "setOverrideStatus", "(I)V");
315 if (gNativeThermalTest_thermalOverrideMethodID == nullptr) {
316 return JNI_ERR;
317 }
318 if (env->RegisterNatives(gNativeThermalTest_class, methodTable,
319 sizeof(methodTable) / sizeof(JNINativeMethod)) != JNI_OK) {
320 return JNI_ERR;
321 }
322 return JNI_VERSION_1_6;
323 }
324