diff options
Diffstat (limited to 'native')
-rw-r--r-- | native/android/Android.bp | 4 | ||||
-rw-r--r-- | native/android/TEST_MAPPING | 4 | ||||
-rw-r--r-- | native/android/display_luts.cpp | 136 | ||||
-rw-r--r-- | native/android/dynamic_instrumentation_manager.cpp | 207 | ||||
-rw-r--r-- | native/android/include_platform/android/dynamic_instrumentation_manager.h | 147 | ||||
-rw-r--r-- | native/android/libandroid.map.txt | 63 | ||||
-rw-r--r-- | native/android/performance_hint.cpp | 805 | ||||
-rw-r--r-- | native/android/surface_control.cpp | 65 | ||||
-rw-r--r-- | native/android/system_fonts.cpp | 2 | ||||
-rw-r--r-- | native/android/system_health.cpp | 371 | ||||
-rw-r--r-- | native/android/tests/performance_hint/PerformanceHintNativeTest.cpp | 412 | ||||
-rw-r--r-- | native/android/tests/system_health/Android.bp | 66 | ||||
-rw-r--r-- | native/android/tests/system_health/NativeSystemHealthUnitTest.cpp | 231 | ||||
-rw-r--r-- | native/android/tests/thermal/NativeThermalUnitTest.cpp | 311 | ||||
-rw-r--r-- | native/android/thermal.cpp | 323 | ||||
-rw-r--r-- | native/graphics/jni/Android.bp | 9 | ||||
-rw-r--r-- | native/graphics/jni/imagedecoder.cpp | 41 |
17 files changed, 2854 insertions, 343 deletions
diff --git a/native/android/Android.bp b/native/android/Android.bp index 3eb99c3387f7..129d6163010e 100644 --- a/native/android/Android.bp +++ b/native/android/Android.bp @@ -55,6 +55,8 @@ cc_library_shared { "surface_control_input_receiver.cpp", "choreographer.cpp", "configuration.cpp", + "display_luts.cpp", + "dynamic_instrumentation_manager.cpp", "hardware_buffer_jni.cpp", "input.cpp", "input_transfer_token.cpp", @@ -71,6 +73,7 @@ cc_library_shared { "surface_control.cpp", "surface_texture.cpp", "system_fonts.cpp", + "system_health.cpp", "trace.cpp", "thermal.cpp", ], @@ -100,6 +103,7 @@ cc_library_shared { "android.hardware.configstore@1.0", "android.hardware.configstore-utils", "android.os.flags-aconfig-cc", + "dynamic_instrumentation_manager_aidl-cpp", "libnativedisplay", "libfmq", ], diff --git a/native/android/TEST_MAPPING b/native/android/TEST_MAPPING index be84574e6a2c..70560a84b88e 100644 --- a/native/android/TEST_MAPPING +++ b/native/android/TEST_MAPPING @@ -16,9 +16,7 @@ { "name": "CtsOsTestCases_cts_performancehintmanagertest", "file_patterns": ["performance_hint.cpp"] - } - ], - "postsubmit": [ + }, { "name": "CtsThermalTestCases", "file_patterns": ["thermal.cpp"] diff --git a/native/android/display_luts.cpp b/native/android/display_luts.cpp new file mode 100644 index 000000000000..b03a718d4a65 --- /dev/null +++ b/native/android/display_luts.cpp @@ -0,0 +1,136 @@ +/* + * 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 "DisplayLuts" + +#include <android/display_luts.h> +#include <display_luts_private.h> +#include <utils/Log.h> + +#include <cmath> + +#define ADISPLAYLUTS_BUFFER_LENGTH_LIMIT (100000) + +#define CHECK_NOT_NULL(name) \ + LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument"); + +ADisplayLutsEntry* ADisplayLutsEntry_createEntry(float* buffer, int32_t length, + ADisplayLuts_Dimension dimension, + ADisplayLuts_SamplingKey key) { + CHECK_NOT_NULL(buffer); + LOG_ALWAYS_FATAL_IF(length >= ADISPLAYLUTS_BUFFER_LENGTH_LIMIT, + "the lut raw buffer length is too big to handle"); + if (dimension != ADISPLAYLUTS_ONE_DIMENSION && dimension != ADISPLAYLUTS_THREE_DIMENSION) { + LOG_ALWAYS_FATAL("the lut dimension is be either 1 or 3"); + } + int32_t size = 0; + if (dimension == ADISPLAYLUTS_THREE_DIMENSION) { + LOG_ALWAYS_FATAL_IF(length % 3 != 0, "the 3d lut raw buffer is not divisible by 3"); + int32_t lengthPerChannel = length / 3; + float sizeForDim = std::cbrt(static_cast<float>(lengthPerChannel)); + LOG_ALWAYS_FATAL_IF(sizeForDim != (int)(sizeForDim), + "the 3d lut buffer length is incorrect"); + size = (int)sizeForDim; + } else { + size = length; + } + LOG_ALWAYS_FATAL_IF(size < 2, "the lut size for each dimension is too small"); + + ADisplayLutsEntry* entry = new ADisplayLutsEntry(); + entry->buffer.data.resize(length); + std::copy(buffer, buffer + length, entry->buffer.data.begin()); + entry->properties = {dimension, size, key}; + + entry->incStrong((void*)ADisplayLutsEntry_createEntry); + return static_cast<ADisplayLutsEntry*>(entry); +} + +void ADisplayLutsEntry_destroy(ADisplayLutsEntry* entry) { + if (entry != NULL) { + entry->decStrong((void*)ADisplayLutsEntry_createEntry); + } +} + +ADisplayLuts_Dimension ADisplayLutsEntry_getDimension(const ADisplayLutsEntry* entry) { + CHECK_NOT_NULL(entry); + return entry->properties.dimension; +} + +int32_t ADisplayLutsEntry_getSize(const ADisplayLutsEntry* entry) { + CHECK_NOT_NULL(entry); + return entry->properties.size; +} + +ADisplayLuts_SamplingKey ADisplayLutsEntry_getSamplingKey(const ADisplayLutsEntry* entry) { + CHECK_NOT_NULL(entry); + return entry->properties.samplingKey; +} + +const float* ADisplayLutsEntry_getBuffer(const ADisplayLutsEntry* _Nonnull entry) { + CHECK_NOT_NULL(entry); + return entry->buffer.data.data(); +} + +ADisplayLuts* ADisplayLuts_create() { + ADisplayLuts* luts = new ADisplayLuts(); + if (luts == NULL) { + delete luts; + return NULL; + } + luts->incStrong((void*)ADisplayLuts_create); + return static_cast<ADisplayLuts*>(luts); +} + +void ADisplayLuts_clearLuts(ADisplayLuts* luts) { + for (auto& entry : luts->entries) { + entry->decStrong((void*)ADisplayLuts_setEntries); // Decrement ref count + } + luts->entries.clear(); + luts->offsets.clear(); + luts->totalBufferSize = 0; +} + +void ADisplayLuts_destroy(ADisplayLuts* luts) { + if (luts != NULL) { + ADisplayLuts_clearLuts(luts); + luts->decStrong((void*)ADisplayLuts_create); + } +} + +void ADisplayLuts_setEntries(ADisplayLuts* luts, ADisplayLutsEntry** entries, int32_t numEntries) { + CHECK_NOT_NULL(luts); + // always clear the previously set lut(s) + ADisplayLuts_clearLuts(luts); + + // do nothing + if (!entries || numEntries == 0) { + return; + } + + LOG_ALWAYS_FATAL_IF(numEntries > 2, "The number of entries should be not over 2!"); + if (numEntries == 2 && entries[0]->properties.dimension != ADISPLAYLUTS_ONE_DIMENSION && + entries[1]->properties.dimension != ADISPLAYLUTS_THREE_DIMENSION) { + LOG_ALWAYS_FATAL("The entries should be 1D and 3D in order!"); + } + + luts->offsets.reserve(numEntries); + luts->entries.reserve(numEntries); + for (int32_t i = 0; i < numEntries; i++) { + luts->offsets.emplace_back(luts->totalBufferSize); + luts->totalBufferSize += entries[i]->buffer.data.size(); + luts->entries.emplace_back(entries[i]); + luts->entries.back()->incStrong((void*)ADisplayLuts_setEntries); + } +}
\ No newline at end of file diff --git a/native/android/dynamic_instrumentation_manager.cpp b/native/android/dynamic_instrumentation_manager.cpp new file mode 100644 index 000000000000..ee2cd6f3fcde --- /dev/null +++ b/native/android/dynamic_instrumentation_manager.cpp @@ -0,0 +1,207 @@ +/* + * 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 "ADynamicInstrumentationManager" +#include <android-base/properties.h> +#include <android/dynamic_instrumentation_manager.h> +#include <android/os/instrumentation/BnOffsetCallback.h> +#include <android/os/instrumentation/ExecutableMethodFileOffsets.h> +#include <android/os/instrumentation/IDynamicInstrumentationManager.h> +#include <android/os/instrumentation/MethodDescriptor.h> +#include <android/os/instrumentation/TargetProcess.h> +#include <binder/Binder.h> +#include <binder/IServiceManager.h> +#include <utils/Log.h> +#include <utils/StrongPointer.h> + +#include <future> +#include <mutex> +#include <optional> +#include <string> +#include <vector> + +namespace android::dynamicinstrumentationmanager { + +using android::os::instrumentation::BnOffsetCallback; +using android::os::instrumentation::ExecutableMethodFileOffsets; + +// Global instance of IDynamicInstrumentationManager, service is obtained only on first use. +static std::mutex mLock; +static sp<os::instrumentation::IDynamicInstrumentationManager> mService; + +sp<os::instrumentation::IDynamicInstrumentationManager> getService() { + std::lock_guard<std::mutex> scoped_lock(mLock); + if (mService == nullptr || !IInterface::asBinder(mService)->isBinderAlive()) { + sp<IBinder> binder = + defaultServiceManager()->waitForService(String16("dynamic_instrumentation")); + mService = interface_cast<os::instrumentation::IDynamicInstrumentationManager>(binder); + } + return mService; +} + +} // namespace android::dynamicinstrumentationmanager + +using namespace android; +using namespace dynamicinstrumentationmanager; + +struct ADynamicInstrumentationManager_TargetProcess { + uid_t uid; + uid_t pid; + std::string processName; + + ADynamicInstrumentationManager_TargetProcess(uid_t uid, pid_t pid, const char* processName) + : uid(uid), pid(pid), processName(processName) {} +}; + +ADynamicInstrumentationManager_TargetProcess* ADynamicInstrumentationManager_TargetProcess_create( + uid_t uid, pid_t pid, const char* processName) { + return new ADynamicInstrumentationManager_TargetProcess(uid, pid, processName); +} + +void ADynamicInstrumentationManager_TargetProcess_destroy( + const ADynamicInstrumentationManager_TargetProcess* instance) { + if (instance == nullptr) return; + delete instance; +} + +struct ADynamicInstrumentationManager_MethodDescriptor { + std::string fqcn; + std::string methodName; + std::vector<std::string> fqParameters; + + ADynamicInstrumentationManager_MethodDescriptor(const char* fqcn, const char* methodName, + const char* fullyQualifiedParameters[], + size_t numParameters) + : fqcn(fqcn), methodName(methodName) { + std::vector<std::string> fqParameters; + fqParameters.reserve(numParameters); + std::copy_n(fullyQualifiedParameters, numParameters, std::back_inserter(fqParameters)); + this->fqParameters = std::move(fqParameters); + } +}; + +ADynamicInstrumentationManager_MethodDescriptor* +ADynamicInstrumentationManager_MethodDescriptor_create(const char* fullyQualifiedClassName, + const char* methodName, + const char* fullyQualifiedParameters[], + size_t numParameters) { + return new ADynamicInstrumentationManager_MethodDescriptor(fullyQualifiedClassName, methodName, + fullyQualifiedParameters, + numParameters); +} + +void ADynamicInstrumentationManager_MethodDescriptor_destroy( + const ADynamicInstrumentationManager_MethodDescriptor* instance) { + if (instance == nullptr) return; + delete instance; +} + +struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets { + std::string containerPath; + uint64_t containerOffset; + uint64_t methodOffset; +}; + +ADynamicInstrumentationManager_ExecutableMethodFileOffsets* +ADynamicInstrumentationManager_ExecutableMethodFileOffsets_create() { + return new ADynamicInstrumentationManager_ExecutableMethodFileOffsets(); +} + +const char* ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath( + const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) { + return instance->containerPath.c_str(); +} + +uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset( + const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) { + return instance->containerOffset; +} + +uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset( + const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) { + return instance->methodOffset; +} + +void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy( + const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) { + if (instance == nullptr) return; + delete instance; +} + +class ResultCallback : public BnOffsetCallback { +public: + ::android::binder::Status onResult( + const ::std::optional<ExecutableMethodFileOffsets>& offsets) override { + promise_.set_value(offsets); + return android::binder::Status::ok(); + } + + std::optional<ExecutableMethodFileOffsets> waitForResult() { + std::future<std::optional<ExecutableMethodFileOffsets>> futureResult = + promise_.get_future(); + auto futureStatus = futureResult.wait_for( + std::chrono::seconds(1 * android::base::HwTimeoutMultiplier())); + if (futureStatus == std::future_status::ready) { + return futureResult.get(); + } else { + return std::nullopt; + } + } + +private: + std::promise<std::optional<ExecutableMethodFileOffsets>> promise_; +}; + +int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets( + const ADynamicInstrumentationManager_TargetProcess* targetProcess, + const ADynamicInstrumentationManager_MethodDescriptor* methodDescriptor, + const ADynamicInstrumentationManager_ExecutableMethodFileOffsets** out) { + android::os::instrumentation::TargetProcess targetProcessParcel; + targetProcessParcel.uid = targetProcess->uid; + targetProcessParcel.pid = targetProcess->pid; + targetProcessParcel.processName = targetProcess->processName; + + android::os::instrumentation::MethodDescriptor methodDescriptorParcel; + methodDescriptorParcel.fullyQualifiedClassName = methodDescriptor->fqcn; + methodDescriptorParcel.methodName = methodDescriptor->methodName; + methodDescriptorParcel.fullyQualifiedParameters = methodDescriptor->fqParameters; + + sp<os::instrumentation::IDynamicInstrumentationManager> service = getService(); + if (service == nullptr) { + return INVALID_OPERATION; + } + + android::sp<ResultCallback> resultCallback = android::sp<ResultCallback>::make(); + binder_status_t result = + service->getExecutableMethodFileOffsets(targetProcessParcel, methodDescriptorParcel, + resultCallback) + .exceptionCode(); + if (result != OK) { + return result; + } + std::optional<ExecutableMethodFileOffsets> offsets = resultCallback->waitForResult(); + if (offsets != std::nullopt) { + auto* value = new ADynamicInstrumentationManager_ExecutableMethodFileOffsets(); + value->containerPath = offsets->containerPath; + value->containerOffset = offsets->containerOffset; + value->methodOffset = offsets->methodOffset; + *out = value; + } else { + *out = nullptr; + } + + return result; +} diff --git a/native/android/include_platform/android/dynamic_instrumentation_manager.h b/native/android/include_platform/android/dynamic_instrumentation_manager.h new file mode 100644 index 000000000000..7bb7615bc3a1 --- /dev/null +++ b/native/android/include_platform/android/dynamic_instrumentation_manager.h @@ -0,0 +1,147 @@ +/* + * 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. + */ + +#ifndef __ADYNAMICINSTRUMENTATIONMANAGER_H__ +#define __ADYNAMICINSTRUMENTATIONMANAGER_H__ + +#include <sys/cdefs.h> +#include <sys/types.h> + +__BEGIN_DECLS + +struct ADynamicInstrumentationManager_MethodDescriptor; +typedef struct ADynamicInstrumentationManager_MethodDescriptor + ADynamicInstrumentationManager_MethodDescriptor; + +struct ADynamicInstrumentationManager_TargetProcess; +typedef struct ADynamicInstrumentationManager_TargetProcess + ADynamicInstrumentationManager_TargetProcess; + +struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets; +typedef struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets + ADynamicInstrumentationManager_ExecutableMethodFileOffsets; + +/** + * Initializes an ADynamicInstrumentationManager_TargetProcess. Caller must clean up when they are + * done with ADynamicInstrumentationManager_TargetProcess_destroy. + * + * @param uid of targeted process. + * @param pid of targeted process. + * @param processName UTF-8 encoded string representing the same process as specified by `pid`. + * Supplied to disambiguate from corner cases that may arise from pid reuse. + * Referenced parameter must outlive the returned + * ADynamicInstrumentationManager_TargetProcess. + */ +ADynamicInstrumentationManager_TargetProcess* _Nullable + ADynamicInstrumentationManager_TargetProcess_create( + uid_t uid, pid_t pid, const char* _Nonnull processName) __INTRODUCED_IN(36); +/** + * Clean up an ADynamicInstrumentationManager_TargetProcess. + * + * @param instance returned from ADynamicInstrumentationManager_TargetProcess_create. + */ +void ADynamicInstrumentationManager_TargetProcess_destroy( + const ADynamicInstrumentationManager_TargetProcess* _Nullable instance) __INTRODUCED_IN(36); + +/** + * Initializes an ADynamicInstrumentationManager_MethodDescriptor. Caller must clean up when they + * are done with ADynamicInstrumentationManager_MethodDescriptor_destroy. + * + * @param fullyQualifiedClassName UTF-8 encoded fqcn of class containing the method. Referenced + * parameter must outlive the returned + * ADynamicInstrumentationManager_MethodDescriptor. + * @param methodName UTF-8 encoded method name. Referenced parameter must outlive the returned + * ADynamicInstrumentationManager_MethodDescriptor. + * @param fullyQualifiedParameters UTF-8 encoded fqcn of parameters of the method's signature, + * or e.g. "int" for primitives. Referenced parameter should + * outlive the returned + * ADynamicInstrumentationManager_MethodDescriptor. + * @param numParameters length of `fullyQualifiedParameters` array. + */ +ADynamicInstrumentationManager_MethodDescriptor* _Nullable + ADynamicInstrumentationManager_MethodDescriptor_create( + const char* _Nonnull fullyQualifiedClassName, const char* _Nonnull methodName, + const char* _Nonnull* _Nonnull fullyQualifiedParameters, size_t numParameters) + __INTRODUCED_IN(36); +/** + * Clean up an ADynamicInstrumentationManager_MethodDescriptor. + * + * @param instance returned from ADynamicInstrumentationManager_MethodDescriptor_create. + */ +void ADynamicInstrumentationManager_MethodDescriptor_destroy( + const ADynamicInstrumentationManager_MethodDescriptor* _Nullable instance) + __INTRODUCED_IN(36); + +/** + * Get the containerPath calculated by + * ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @return The OS path of the containing file as a UTF-8 string, which has the same lifetime + * as the ADynamicInstrumentationManager_ExecutableMethodFileOffsets instance passed + * as a param. + */ +const char* _Nullable ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath( + const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance) + __INTRODUCED_IN(36); +/** + * Get the containerOffset calculated by + * ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @return The absolute address of the containing file within remote the process' virtual memory + * space. + */ +uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset( + const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance) + __INTRODUCED_IN(36); +/** + * Get the methodOffset calculated by ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @return The offset of the method within the container whose address is returned by + * ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset. + */ +uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset( + const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance) + __INTRODUCED_IN(36); +/** + * Clean up an ADynamicInstrumentationManager_ExecutableMethodFileOffsets. + * + * @param instance returned from ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + */ +void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy( + const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nullable instance) + __INTRODUCED_IN(36); +/** + * Provides ART metadata about the described java method within the target process. + * + * @param targetProcess describes for which process the data is requested. + * @param methodDescriptor describes the targeted method. + * @param out will be populated with the data if successful. A nullptr combined + * with an OK status means that the program method is defined, but the offset + * info was unavailable because it is not AOT compiled. Caller owns `out` and + * should clean it up with + * ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy. + * @return status indicating success or failure. The values correspond to the `binder_exception_t` + * enum values from <android/binder_status.h>. + */ +int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets( + const ADynamicInstrumentationManager_TargetProcess* _Nonnull targetProcess, + const ADynamicInstrumentationManager_MethodDescriptor* _Nonnull methodDescriptor, + const ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nullable* _Nonnull out) + __INTRODUCED_IN(36); + +__END_DECLS + +#endif // __ADYNAMICINSTRUMENTATIONMANAGER_H__ diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 202535d45191..49cbd7181d77 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -4,6 +4,15 @@ LIBANDROID { AActivityManager_removeUidImportanceListener; # systemapi introduced=31 AActivityManager_isUidActive; # systemapi introduced=31 AActivityManager_getUidImportance; # systemapi introduced=31 + ADynamicInstrumentationManager_TargetProcess_create; # systemapi + ADynamicInstrumentationManager_TargetProcess_destroy; # systemapi + ADynamicInstrumentationManager_MethodDescriptor_create; # systemapi + ADynamicInstrumentationManager_MethodDescriptor_destroy; # systemapi + ADynamicInstrumentationManager_getExecutableMethodFileOffsets; # systemapi + ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath; # systemapi + ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset; # systemapi + ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset; # systemapi + ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy; # systemapi AAssetDir_close; AAssetDir_getNextFileName; AAssetDir_rewind; @@ -86,6 +95,15 @@ LIBANDROID { AConfiguration_setTouchscreen; AConfiguration_setUiModeNight; AConfiguration_setUiModeType; + ADisplayLuts_create; # introduced=36 + ADisplayLuts_setEntries; # introduced=36 + ADisplayLuts_destroy; # introduced=36 + ADisplayLutsEntry_createEntry; # introduced=36 + ADisplayLutsEntry_getDimension; # introduced=36 + ADisplayLutsEntry_getSize; # introduced=36 + ADisplayLutsEntry_getSamplingKey; # introduced=36 + ADisplayLutsEntry_getBuffer; # introduced=36 + ADisplayLutsEntry_destroy; # introduced=36 AInputEvent_getDeviceId; AInputEvent_getSource; AInputEvent_getType; @@ -290,6 +308,7 @@ LIBANDROID { ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29 ASurfaceTransaction_setExtendedRangeBrightness; # introduced=UpsideDownCake ASurfaceTransaction_setDesiredHdrHeadroom; # introduced=VanillaIceCream + ASurfaceTransaction_setLuts; # introduced=36 ASurfaceTransaction_setOnComplete; # introduced=29 ASurfaceTransaction_setOnCommit; # introduced=31 ASurfaceTransaction_setPosition; # introduced=31 @@ -301,6 +320,26 @@ LIBANDROID { ASystemFontIterator_open; # introduced=29 ASystemFontIterator_close; # introduced=29 ASystemFontIterator_next; # introduced=29 + ASystemHealth_getMaxCpuHeadroomTidsSize; # introduced=36 + ASystemHealth_getCpuHeadroomCalculationWindowRange; # introduced=36 + ASystemHealth_getGpuHeadroomCalculationWindowRange; # introduced=36 + ASystemHealth_getCpuHeadroom; # introduced=36 + ASystemHealth_getGpuHeadroom; # introduced=36 + ASystemHealth_getCpuHeadroomMinIntervalMillis; # introduced=36 + ASystemHealth_getGpuHeadroomMinIntervalMillis; # introduced=36 + ACpuHeadroomParams_create; # introduced=36 + ACpuHeadroomParams_destroy; # introduced=36 + ACpuHeadroomParams_setCalculationType; # introduced=36 + ACpuHeadroomParams_getCalculationType; # introduced=36 + ACpuHeadroomParams_setCalculationWindowMillis; # introduced=36 + ACpuHeadroomParams_getCalculationWindowMillis; # introduced=36 + ACpuHeadroomParams_setTids; # introduced=36 + AGpuHeadroomParams_create; # introduced=36 + AGpuHeadroomParams_destroy; # introduced=36 + AGpuHeadroomParams_setCalculationType; # introduced=36 + AGpuHeadroomParams_getCalculationType; # introduced=36 + AGpuHeadroomParams_setCalculationWindowMillis; # introduced=36 + AGpuHeadroomParams_getCalculationWindowMillis; # introduced=36 AFont_close; # introduced=29 AFont_getFontFilePath; # introduced=29 AFont_getWeight; # introduced=29 @@ -342,21 +381,39 @@ LIBANDROID { AThermal_unregisterThermalStatusListener; # introduced=30 AThermal_getThermalHeadroom; # introduced=31 AThermal_getThermalHeadroomThresholds; # introduced=VanillaIceCream + AThermal_registerThermalHeadroomListener; # introduced=36 + AThermal_unregisterThermalHeadroomListener; # introduced=36 APerformanceHint_getManager; # introduced=Tiramisu APerformanceHint_createSession; # introduced=Tiramisu APerformanceHint_getPreferredUpdateRateNanos; # introduced=Tiramisu + APerformanceHint_getMaxGraphicsPipelineThreadsCount; # introduced=36 APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu APerformanceHint_closeSession; # introduced=Tiramisu APerformanceHint_setThreads; # introduced=UpsideDownCake APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream + APerformanceHint_createSessionUsingConfig; # introduced=36 + APerformanceHint_notifyWorkloadIncrease; # introduced=36 + APerformanceHint_notifyWorkloadReset; # introduced=36 + APerformanceHint_notifyWorkloadSpike; # introduced=36 + APerformanceHint_borrowSessionFromJava; # introduced=36 + APerformanceHint_setNativeSurfaces; # introduced=36 + APerformanceHint_isFeatureSupported; # introduced=36 AWorkDuration_create; # introduced=VanillaIceCream AWorkDuration_release; # introduced=VanillaIceCream AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream AWorkDuration_setActualTotalDurationNanos; # introduced=VanillaIceCream AWorkDuration_setActualCpuDurationNanos; # introduced=VanillaIceCream AWorkDuration_setActualGpuDurationNanos; # introduced=VanillaIceCream + ASessionCreationConfig_create; # introduced=36 + ASessionCreationConfig_release; # introduced=36 + ASessionCreationConfig_setTids; # introduced=36 + ASessionCreationConfig_setTargetWorkDurationNanos; # introduced=36 + ASessionCreationConfig_setPreferPowerEfficiency; # introduced=36 + ASessionCreationConfig_setGraphicsPipeline; # introduced=36 + ASessionCreationConfig_setNativeSurfaces; # introduced=36 + ASessionCreationConfig_setUseAutoTiming; # introduced=36 local: *; }; @@ -364,11 +421,17 @@ LIBANDROID { LIBANDROID_PLATFORM { global: AThermal_setIThermalServiceForTesting; + ASystemHealth_setIHintManagerForTesting; APerformanceHint_setIHintManagerForTesting; APerformanceHint_sendHint; APerformanceHint_getThreadIds; APerformanceHint_createSessionInternal; + APerformanceHint_createSessionUsingConfigInternal; APerformanceHint_setUseFMQForTesting; + APerformanceHint_getRateLimiterPropertiesForTesting; + APerformanceHint_setUseNewLoadHintBehaviorForTesting; + APerformanceHint_closeSessionFromJava; + APerformanceHint_createSessionFromJava; extern "C++" { ASurfaceControl_registerSurfaceStatsListener*; ASurfaceControl_unregisterSurfaceStatsListener*; diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index 095d7d1145ae..1e6a7b7f2810 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -22,23 +22,35 @@ #include <aidl/android/hardware/power/SessionHint.h> #include <aidl/android/hardware/power/SessionMode.h> #include <aidl/android/hardware/power/SessionTag.h> +#include <aidl/android/hardware/power/SupportInfo.h> #include <aidl/android/hardware/power/WorkDuration.h> #include <aidl/android/hardware/power/WorkDurationFixedV1.h> #include <aidl/android/os/IHintManager.h> #include <aidl/android/os/IHintSession.h> +#include <aidl/android/os/SessionCreationConfig.h> #include <android-base/stringprintf.h> #include <android-base/thread_annotations.h> +#include <android/binder_libbinder.h> #include <android/binder_manager.h> #include <android/binder_status.h> +#include <android/native_window.h> #include <android/performance_hint.h> +#include <android/surface_control.h> #include <android/trace.h> #include <android_os.h> +#include <cutils/trace.h> #include <fmq/AidlMessageQueue.h> +#include <gui/Surface.h> +#include <gui/SurfaceComposerClient.h> +#include <gui/SurfaceControl.h> #include <inttypes.h> +#include <jni_wrappers.h> #include <performance_hint_private.h> #include <utils/SystemClock.h> #include <chrono> +#include <format> +#include <future> #include <set> #include <utility> #include <vector> @@ -59,8 +71,41 @@ using android::base::StringPrintf; struct APerformanceHintSession; -constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count(); struct AWorkDuration : public hal::WorkDuration {}; +struct ASessionCreationConfig : public SessionCreationConfig { + std::vector<wp<IBinder>> layers{}; + bool hasMode(hal::SessionMode mode) { + return std::find(modesToEnable.begin(), modesToEnable.end(), mode) != modesToEnable.end(); + } + void setMode(hal::SessionMode mode, bool enabled) { + if (hasMode(mode)) { + if (!enabled) { + std::erase(modesToEnable, mode); + } + } else if (enabled) { + modesToEnable.push_back(mode); + } + } +}; + +// A pair of values that determine the behavior of the +// load hint rate limiter, to only allow "X hints every Y seconds" +constexpr int64_t kLoadHintInterval = std::chrono::nanoseconds(2s).count(); +constexpr double kMaxLoadHintsPerInterval = 20; +// Replenish rate is used for new rate limiting behavior, it currently replenishes at a rate of +// 20 / 2s = 1 per 100us, which is the same limit as before, just enforced differently +constexpr double kReplenishRate = kMaxLoadHintsPerInterval / static_cast<double>(kLoadHintInterval); +constexpr int64_t kSendHintTimeout = kLoadHintInterval / kMaxLoadHintsPerInterval; +bool kForceNewHintBehavior = false; + +template <class T> +constexpr int32_t enum_size() { + return static_cast<int32_t>(*(ndk::enum_range<T>().end() - 1)) + 1; +} + +bool useNewLoadHintBehavior() { + return android::os::adpf_use_load_hints() || kForceNewHintBehavior; +} // Shared lock for the whole PerformanceHintManager and sessions static std::mutex sHintMutex = std::mutex{}; @@ -75,7 +120,8 @@ public: hal::WorkDuration* durations, size_t count) REQUIRES(sHintMutex); bool updateTargetWorkDuration(std::optional<hal::SessionConfig>& config, int64_t targetDurationNanos) REQUIRES(sHintMutex); - bool sendHint(std::optional<hal::SessionConfig>& config, SessionHint hint) REQUIRES(sHintMutex); + bool sendHints(std::optional<hal::SessionConfig>& config, std::vector<hal::SessionHint>& hint, + int64_t now) REQUIRES(sHintMutex); bool setMode(std::optional<hal::SessionConfig>& config, hal::SessionMode, bool enabled) REQUIRES(sHintMutex); void setToken(ndk::SpAIBinder& token); @@ -85,10 +131,11 @@ public: private: template <HalChannelMessageContents::Tag T, bool urgent = false, class C = HalChannelMessageContents::_at<T>> - bool sendMessages(std::optional<hal::SessionConfig>& config, C* message, size_t count = 1) - REQUIRES(sHintMutex); + bool sendMessages(std::optional<hal::SessionConfig>& config, C* message, size_t count = 1, + int64_t now = ::android::uptimeNanos()) REQUIRES(sHintMutex); template <HalChannelMessageContents::Tag T, class C = HalChannelMessageContents::_at<T>> - void writeBuffer(C* message, hal::SessionConfig& config, size_t count) REQUIRES(sHintMutex); + void writeBuffer(C* message, hal::SessionConfig& config, size_t count, int64_t now) + REQUIRES(sHintMutex); bool isActiveLocked() REQUIRES(sHintMutex); bool updatePersistentTransaction() REQUIRES(sHintMutex); @@ -104,56 +151,99 @@ private: size_t mAvailableSlots GUARDED_BY(sHintMutex) = 0; bool mHalSupported = true; HalMessageQueue::MemTransaction mFmqTransaction GUARDED_BY(sHintMutex); + std::future<bool> mChannelCreationFinished; +}; + +struct SupportInfoWrapper : public hal::SupportInfo { + bool isSessionModeSupported(hal::SessionMode mode); + bool isSessionHintSupported(hal::SessionHint hint); + +private: + template <class T> + bool getEnumSupportFromBitfield(T& enumValue, int64_t& supportBitfield) { + // extract the bit corresponding to the enum by shifting the bitfield + // over that much and cutting off any extra values + return (supportBitfield >> static_cast<int>(enumValue)) % 2; + } +}; + +class HintManagerClient : public IHintManager::BnHintManagerClient { +public: + // Currently a no-op that exists for FMQ init to call in the future + ndk::ScopedAStatus receiveChannelConfig(const hal::ChannelConfig&) { + return ndk::ScopedAStatus::ok(); + } }; struct APerformanceHintManager { public: static APerformanceHintManager* getInstance(); - APerformanceHintManager(std::shared_ptr<IHintManager>& service, int64_t preferredRateNanos); + APerformanceHintManager(std::shared_ptr<IHintManager>& service, + IHintManager::HintManagerClientData&& clientData, + std::shared_ptr<HintManagerClient> callbackClient); APerformanceHintManager() = delete; ~APerformanceHintManager(); APerformanceHintSession* createSession(const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos, - hal::SessionTag tag = hal::SessionTag::APP); + hal::SessionTag tag = hal::SessionTag::APP, + bool isJava = false); + APerformanceHintSession* getSessionFromJava(JNIEnv* _Nonnull env, jobject _Nonnull sessionObj); + + int createSessionUsingConfig(ASessionCreationConfig* sessionCreationConfig, + APerformanceHintSession** sessionPtr, + hal::SessionTag tag = hal::SessionTag::APP, bool isJava = false); int64_t getPreferredRateNanos() const; + int32_t getMaxGraphicsPipelineThreadsCount(); FMQWrapper& getFMQWrapper(); + bool canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) REQUIRES(sHintMutex); + void initJava(JNIEnv* _Nonnull env); + template <class T> + static void layersFromNativeSurfaces(ANativeWindow** windows, int numWindows, + ASurfaceControl** controls, int numSurfaceControls, + std::vector<T>& out); + ndk::SpAIBinder& getToken(); + SupportInfoWrapper& getSupportInfo(); + bool isFeatureSupported(APerformanceHintFeature feature); private: - // Necessary to create an empty binder object - static void* tokenStubOnCreate(void*) { - return nullptr; - } - static void tokenStubOnDestroy(void*) {} - static binder_status_t tokenStubOnTransact(AIBinder*, transaction_code_t, const AParcel*, - AParcel*) { - return STATUS_OK; - } - static APerformanceHintManager* create(std::shared_ptr<IHintManager> iHintManager); std::shared_ptr<IHintManager> mHintManager; + std::shared_ptr<HintManagerClient> mCallbackClient; + IHintManager::HintManagerClientData mClientData; + SupportInfoWrapper mSupportInfoWrapper; ndk::SpAIBinder mToken; - const int64_t mPreferredRateNanos; FMQWrapper mFMQWrapper; + double mHintBudget = kMaxLoadHintsPerInterval; + int64_t mLastBudgetReplenish = 0; + bool mJavaInitialized = false; + jclass mJavaSessionClazz; + jfieldID mJavaSessionNativePtr; }; struct APerformanceHintSession { public: APerformanceHintSession(std::shared_ptr<IHintManager> hintManager, std::shared_ptr<IHintSession> session, int64_t preferredRateNanos, - int64_t targetDurationNanos, + int64_t targetDurationNanos, bool isJava, std::optional<hal::SessionConfig> sessionConfig); APerformanceHintSession() = delete; ~APerformanceHintSession(); int updateTargetWorkDuration(int64_t targetDurationNanos); int reportActualWorkDuration(int64_t actualDurationNanos); - int sendHint(SessionHint hint); + int sendHints(std::vector<hal::SessionHint>& hints, int64_t now, const char* debugName); + int notifyWorkloadIncrease(bool cpu, bool gpu, const char* debugName); + int notifyWorkloadReset(bool cpu, bool gpu, const char* debugName); + int notifyWorkloadSpike(bool cpu, bool gpu, const char* debugName); int setThreads(const int32_t* threadIds, size_t size); int getThreadIds(int32_t* const threadIds, size_t* size); int setPreferPowerEfficiency(bool enabled); int reportActualWorkDuration(AWorkDuration* workDuration); + bool isJava(); + status_t setNativeSurfaces(ANativeWindow** windows, size_t numWindows, + ASurfaceControl** controls, size_t numSurfaceControls); private: friend struct APerformanceHintManager; @@ -171,17 +261,23 @@ private: // Last target hit timestamp int64_t mLastTargetMetTimestamp GUARDED_BY(sHintMutex); // Last hint reported from sendHint indexed by hint value + // This is only used by the old rate limiter impl and is replaced + // with the new rate limiter under a flag std::vector<int64_t> mLastHintSentTimestamp GUARDED_BY(sHintMutex); // Cached samples std::vector<hal::WorkDuration> mActualWorkDurations GUARDED_BY(sHintMutex); + // Is this session backing an SDK wrapper object + const bool mIsJava; std::string mSessionName; static int64_t sIDCounter GUARDED_BY(sHintMutex); // The most recent set of thread IDs std::vector<int32_t> mLastThreadIDs GUARDED_BY(sHintMutex); - std::optional<hal::SessionConfig> mSessionConfig GUARDED_BY(sHintMutex); + std::optional<hal::SessionConfig> mSessionConfig; // Tracing helpers - void traceThreads(std::vector<int32_t>& tids) REQUIRES(sHintMutex); + void traceThreads(const std::vector<int32_t>& tids) REQUIRES(sHintMutex); void tracePowerEfficient(bool powerEfficient); + void traceGraphicsPipeline(bool graphicsPipeline); + void traceModes(const std::vector<hal::SessionMode>& modesToEnable); void traceActualDuration(int64_t actualDuration); void traceBatchSize(size_t batchSize); void traceTargetDuration(int64_t targetDuration); @@ -199,14 +295,25 @@ static FMQWrapper& getFMQ() { return APerformanceHintManager::getInstance()->getFMQWrapper(); } +// ===================================== SupportInfoWrapper implementation + +bool SupportInfoWrapper::isSessionHintSupported(hal::SessionHint hint) { + return getEnumSupportFromBitfield(hint, sessionHints); +} + +bool SupportInfoWrapper::isSessionModeSupported(hal::SessionMode mode) { + return getEnumSupportFromBitfield(mode, sessionModes); +} + // ===================================== APerformanceHintManager implementation APerformanceHintManager::APerformanceHintManager(std::shared_ptr<IHintManager>& manager, - int64_t preferredRateNanos) - : mHintManager(std::move(manager)), mPreferredRateNanos(preferredRateNanos) { - static AIBinder_Class* tokenBinderClass = - AIBinder_Class_define("phm_token", tokenStubOnCreate, tokenStubOnDestroy, - tokenStubOnTransact); - mToken = ndk::SpAIBinder(AIBinder_new(tokenBinderClass, nullptr)); + IHintManager::HintManagerClientData&& clientData, + std::shared_ptr<HintManagerClient> callbackClient) + : mHintManager(std::move(manager)), + mCallbackClient(callbackClient), + mClientData(clientData), + mSupportInfoWrapper(clientData.supportInfo), + mToken(callbackClient->asBinder()) { if (mFMQWrapper.isSupported()) { mFMQWrapper.setToken(mToken); mFMQWrapper.startChannel(mHintManager.get()); @@ -218,6 +325,8 @@ APerformanceHintManager::~APerformanceHintManager() { } APerformanceHintManager* APerformanceHintManager::getInstance() { + static std::once_flag creationFlag; + static APerformanceHintManager* instance = nullptr; if (gHintManagerForTesting) { return gHintManagerForTesting.get(); } @@ -226,7 +335,7 @@ APerformanceHintManager* APerformanceHintManager::getInstance() { std::shared_ptr<APerformanceHintManager>(create(*gIHintManagerForTesting)); return gHintManagerForTesting.get(); } - static APerformanceHintManager* instance = create(nullptr); + std::call_once(creationFlag, []() { instance = create(nullptr); }); return instance; } @@ -239,62 +348,201 @@ APerformanceHintManager* APerformanceHintManager::create(std::shared_ptr<IHintMa ALOGE("%s: PerformanceHint service is not ready ", __FUNCTION__); return nullptr; } - int64_t preferredRateNanos = -1L; - ndk::ScopedAStatus ret = manager->getHintSessionPreferredRate(&preferredRateNanos); + std::shared_ptr<HintManagerClient> client = ndk::SharedRefBase::make<HintManagerClient>(); + IHintManager::HintManagerClientData clientData; + ndk::ScopedAStatus ret = manager->registerClient(client, &clientData); if (!ret.isOk()) { - ALOGE("%s: PerformanceHint cannot get preferred rate. %s", __FUNCTION__, ret.getMessage()); + ALOGE("%s: PerformanceHint is not supported. %s", __FUNCTION__, ret.getMessage()); return nullptr; } - if (preferredRateNanos <= 0) { - preferredRateNanos = -1L; + if (clientData.preferredRateNanos <= 0) { + clientData.preferredRateNanos = -1L; + } + return new APerformanceHintManager(manager, std::move(clientData), client); +} + +bool APerformanceHintManager::canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) { + mHintBudget = + std::min(kMaxLoadHintsPerInterval, + mHintBudget + + static_cast<double>(now - mLastBudgetReplenish) * kReplenishRate); + mLastBudgetReplenish = now; + + // If this youngest timestamp isn't older than the timeout time, we can't send + if (hints.size() > mHintBudget) { + return false; } - return new APerformanceHintManager(manager, preferredRateNanos); + mHintBudget -= hints.size(); + return true; } APerformanceHintSession* APerformanceHintManager::createSession( const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos, - hal::SessionTag tag) { - std::vector<int32_t> tids(threadIds, threadIds + size); - std::shared_ptr<IHintSession> session; + hal::SessionTag tag, bool isJava) { ndk::ScopedAStatus ret; hal::SessionConfig sessionConfig{.id = -1}; - ret = mHintManager->createHintSessionWithConfig(mToken, tids, initialTargetWorkDurationNanos, - tag, &sessionConfig, &session); - if (!ret.isOk() || !session) { + ASessionCreationConfig creationConfig{{ + .tids = std::vector<int32_t>(threadIds, threadIds + size), + .targetWorkDurationNanos = initialTargetWorkDurationNanos, + }}; + + APerformanceHintSession* sessionOut; + APerformanceHintManager::createSessionUsingConfig(&creationConfig, &sessionOut, tag, isJava); + return sessionOut; +} + +int APerformanceHintManager::createSessionUsingConfig(ASessionCreationConfig* sessionCreationConfig, + APerformanceHintSession** sessionOut, + hal::SessionTag tag, bool isJava) { + hal::SessionConfig sessionConfig{.id = -1}; + ndk::ScopedAStatus ret; + + // Hold the tokens weakly until we actually need them, + // then promote them, then drop all strong refs after + if (!sessionCreationConfig->layers.empty()) { + for (auto&& layerIter = sessionCreationConfig->layers.begin(); + layerIter != sessionCreationConfig->layers.end();) { + sp<IBinder> promoted = layerIter->promote(); + if (promoted == nullptr) { + layerIter = sessionCreationConfig->layers.erase(layerIter); + } else { + sessionCreationConfig->layerTokens.push_back( + ndk::SpAIBinder(AIBinder_fromPlatformBinder(promoted.get()))); + ++layerIter; + } + } + } + + bool autoCpu = sessionCreationConfig->hasMode(hal::SessionMode::AUTO_CPU); + bool autoGpu = sessionCreationConfig->hasMode(hal::SessionMode::AUTO_GPU); + + if (autoCpu || autoGpu) { + LOG_ALWAYS_FATAL_IF(!sessionCreationConfig->hasMode(hal::SessionMode::GRAPHICS_PIPELINE), + "Automatic session timing enabled without graphics pipeline mode"); + } + + if (autoCpu && !mSupportInfoWrapper.isSessionModeSupported(hal::SessionMode::AUTO_CPU)) { + ALOGE("Automatic CPU timing enabled but not supported"); + return ENOTSUP; + } + + if (autoGpu && !mSupportInfoWrapper.isSessionModeSupported(hal::SessionMode::AUTO_GPU)) { + ALOGE("Automatic GPU timing enabled but not supported"); + return ENOTSUP; + } + + IHintManager::SessionCreationReturn returnValue; + ret = mHintManager->createHintSessionWithConfig(mToken, tag, + *static_cast<SessionCreationConfig*>( + sessionCreationConfig), + &sessionConfig, &returnValue); + + sessionCreationConfig->layerTokens.clear(); + + if (!ret.isOk() || !returnValue.session) { ALOGE("%s: PerformanceHint cannot create session. %s", __FUNCTION__, ret.getMessage()); - return nullptr; + switch (ret.getExceptionCode()) { + case binder::Status::EX_UNSUPPORTED_OPERATION: + return ENOTSUP; + case binder::Status::EX_ILLEGAL_ARGUMENT: + return EINVAL; + default: + return EPIPE; + } } - auto out = new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos, - initialTargetWorkDurationNanos, + + auto out = new APerformanceHintSession(mHintManager, std::move(returnValue.session), + mClientData.preferredRateNanos, + sessionCreationConfig->targetWorkDurationNanos, isJava, sessionConfig.id == -1 ? std::nullopt : std::make_optional<hal::SessionConfig>( std::move(sessionConfig))); + + *sessionOut = out; + std::scoped_lock lock(sHintMutex); - out->traceThreads(tids); - out->traceTargetDuration(initialTargetWorkDurationNanos); - out->tracePowerEfficient(false); + out->traceThreads(sessionCreationConfig->tids); + out->traceTargetDuration(sessionCreationConfig->targetWorkDurationNanos); + out->traceModes(sessionCreationConfig->modesToEnable); + + if (returnValue.pipelineThreadLimitExceeded) { + ALOGE("Graphics pipeline session thread limit exceeded!"); + return EBUSY; + } + + return 0; +} + +APerformanceHintSession* APerformanceHintManager::getSessionFromJava(JNIEnv* env, + jobject sessionObj) { + initJava(env); + LOG_ALWAYS_FATAL_IF(!env->IsInstanceOf(sessionObj, mJavaSessionClazz), + "Wrong java type passed to APerformanceHint_getSessionFromJava"); + APerformanceHintSession* out = reinterpret_cast<APerformanceHintSession*>( + env->GetLongField(sessionObj, mJavaSessionNativePtr)); + LOG_ALWAYS_FATAL_IF(out == nullptr, "Java-wrapped native hint session is nullptr"); + LOG_ALWAYS_FATAL_IF(!out->isJava(), "Unmanaged native hint session returned from Java SDK"); return out; } int64_t APerformanceHintManager::getPreferredRateNanos() const { - return mPreferredRateNanos; + return mClientData.preferredRateNanos; +} + +int32_t APerformanceHintManager::getMaxGraphicsPipelineThreadsCount() { + return mClientData.maxGraphicsPipelineThreads; } FMQWrapper& APerformanceHintManager::getFMQWrapper() { return mFMQWrapper; } -// ===================================== APerformanceHintSession implementation +void APerformanceHintManager::initJava(JNIEnv* _Nonnull env) { + if (mJavaInitialized) { + return; + } + jclass sessionClazz = FindClassOrDie(env, "android/os/PerformanceHintManager$Session"); + mJavaSessionClazz = MakeGlobalRefOrDie(env, sessionClazz); + mJavaSessionNativePtr = GetFieldIDOrDie(env, mJavaSessionClazz, "mNativeSessionPtr", "J"); + mJavaInitialized = true; +} + +ndk::SpAIBinder& APerformanceHintManager::getToken() { + return mToken; +} + +SupportInfoWrapper& APerformanceHintManager::getSupportInfo() { + return mSupportInfoWrapper; +} -constexpr int kNumEnums = - ndk::enum_range<hal::SessionHint>().end() - ndk::enum_range<hal::SessionHint>().begin(); +bool APerformanceHintManager::isFeatureSupported(APerformanceHintFeature feature) { + switch (feature) { + case (APERF_HINT_SESSIONS): + return mSupportInfoWrapper.usesSessions; + case (APERF_HINT_POWER_EFFICIENCY): + return mSupportInfoWrapper.isSessionModeSupported(hal::SessionMode::POWER_EFFICIENCY); + case (APERF_HINT_SURFACE_BINDING): + return mSupportInfoWrapper.compositionData.isSupported; + case (APERF_HINT_GRAPHICS_PIPELINE): + return mSupportInfoWrapper.isSessionModeSupported(hal::SessionMode::GRAPHICS_PIPELINE); + case (APERF_HINT_AUTO_CPU): + return mSupportInfoWrapper.isSessionModeSupported(hal::SessionMode::AUTO_CPU); + case (APERF_HINT_AUTO_GPU): + return mSupportInfoWrapper.isSessionModeSupported(hal::SessionMode::AUTO_GPU); + default: + return false; + } +} +// ===================================== APerformanceHintSession implementation + +constexpr int kNumEnums = enum_size<hal::SessionHint>(); APerformanceHintSession::APerformanceHintSession(std::shared_ptr<IHintManager> hintManager, std::shared_ptr<IHintSession> session, int64_t preferredRateNanos, - int64_t targetDurationNanos, + int64_t targetDurationNanos, bool isJava, std::optional<hal::SessionConfig> sessionConfig) : mHintManager(hintManager), mHintSession(std::move(session)), @@ -303,6 +551,7 @@ APerformanceHintSession::APerformanceHintSession(std::shared_ptr<IHintManager> h mFirstTargetMetTimestamp(0), mLastTargetMetTimestamp(0), mLastHintSentTimestamp(std::vector<int64_t>(kNumEnums, 0)), + mIsJava(isJava), mSessionConfig(sessionConfig) { if (sessionConfig->id > INT32_MAX) { ALOGE("Session ID too large, must fit 32-bit integer"); @@ -319,10 +568,6 @@ APerformanceHintSession::~APerformanceHintSession() { } int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNanos) { - if (targetDurationNanos <= 0) { - ALOGE("%s: targetDurationNanos must be positive", __FUNCTION__); - return EINVAL; - } std::scoped_lock lock(sHintMutex); if (mTargetDurationNanos == targetDurationNanos) { return 0; @@ -353,35 +598,110 @@ int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNano .workPeriodStartTimestampNanos = 0, .cpuDurationNanos = actualDurationNanos, .gpuDurationNanos = 0}; - return reportActualWorkDurationInternal(static_cast<AWorkDuration*>(&workDuration)); } -int APerformanceHintSession::sendHint(SessionHint hint) { - std::scoped_lock lock(sHintMutex); - if (hint < 0 || hint >= static_cast<int32_t>(mLastHintSentTimestamp.size())) { - ALOGE("%s: invalid session hint %d", __FUNCTION__, hint); - return EINVAL; - } - int64_t now = uptimeNanos(); +bool APerformanceHintSession::isJava() { + return mIsJava; +} + +int APerformanceHintSession::sendHints(std::vector<hal::SessionHint>& hints, int64_t now, + const char*) { + auto& supportInfo = APerformanceHintManager::getInstance()->getSupportInfo(); - // Limit sendHint to a pre-detemined rate for safety - if (now < (mLastHintSentTimestamp[hint] + SEND_HINT_TIMEOUT)) { + // Drop all unsupported hints, there's not much point reporting errors or warnings for this + std::erase_if(hints, + [&](hal::SessionHint hint) { return !supportInfo.isSessionHintSupported(hint); }); + + if (hints.empty()) { + // We successfully sent all hints we were able to, technically return 0; } - if (!getFMQ().sendHint(mSessionConfig, hint)) { - ndk::ScopedAStatus ret = mHintSession->sendHint(hint); + for (auto&& hint : hints) { + LOG_ALWAYS_FATAL_IF(static_cast<int32_t>(hint) < 0 || + static_cast<int32_t>(hint) >= kNumEnums, + "%s: invalid session hint %d", __FUNCTION__, hint); + } - if (!ret.isOk()) { - ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.getMessage()); - return EPIPE; + std::scoped_lock lock(sHintMutex); + if (useNewLoadHintBehavior()) { + if (!APerformanceHintManager::getInstance()->canSendLoadHints(hints, now)) { + return EBUSY; + } + } + // keep old rate limiter behavior for legacy flag + else { + for (auto&& hint : hints) { + if (now < (mLastHintSentTimestamp[static_cast<int32_t>(hint)] + kSendHintTimeout)) { + return EBUSY; + } + } + } + + if (!getFMQ().sendHints(mSessionConfig, hints, now)) { + for (auto&& hint : hints) { + ndk::ScopedAStatus ret = mHintSession->sendHint(static_cast<int32_t>(hint)); + + if (!ret.isOk()) { + ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.getMessage()); + return EPIPE; + } + } + } + + if (!useNewLoadHintBehavior()) { + for (auto&& hint : hints) { + mLastHintSentTimestamp[static_cast<int32_t>(hint)] = now; } } - mLastHintSentTimestamp[hint] = now; + + if (ATrace_isEnabled()) { + ATRACE_INSTANT("Sending load hint"); + } + return 0; } +int APerformanceHintSession::notifyWorkloadIncrease(bool cpu, bool gpu, const char* debugName) { + std::vector<hal::SessionHint> hints(2); + hints.clear(); + if (cpu) { + hints.push_back(hal::SessionHint::CPU_LOAD_UP); + } + if (gpu) { + hints.push_back(hal::SessionHint::GPU_LOAD_UP); + } + int64_t now = ::android::uptimeNanos(); + return sendHints(hints, now, debugName); +} + +int APerformanceHintSession::notifyWorkloadReset(bool cpu, bool gpu, const char* debugName) { + std::vector<hal::SessionHint> hints(2); + hints.clear(); + if (cpu) { + hints.push_back(hal::SessionHint::CPU_LOAD_RESET); + } + if (gpu) { + hints.push_back(hal::SessionHint::GPU_LOAD_RESET); + } + int64_t now = ::android::uptimeNanos(); + return sendHints(hints, now, debugName); +} + +int APerformanceHintSession::notifyWorkloadSpike(bool cpu, bool gpu, const char* debugName) { + std::vector<hal::SessionHint> hints(2); + hints.clear(); + if (cpu) { + hints.push_back(hal::SessionHint::CPU_LOAD_SPIKE); + } + if (gpu) { + hints.push_back(hal::SessionHint::GPU_LOAD_SPIKE); + } + int64_t now = ::android::uptimeNanos(); + return sendHints(hints, now, debugName); +} + int APerformanceHintSession::setThreads(const int32_t* threadIds, size_t size) { if (size == 0) { ALOGE("%s: the list of thread ids must not be empty.", __FUNCTION__); @@ -389,7 +709,9 @@ int APerformanceHintSession::setThreads(const int32_t* threadIds, size_t size) { } std::vector<int32_t> tids(threadIds, threadIds + size); ndk::ScopedAStatus ret = mHintManager->setHintSessionThreads(mHintSession, tids); - if (!ret.isOk()) { + + // Illegal state means there were too many graphics pipeline threads + if (!ret.isOk() && ret.getExceptionCode() != EX_SERVICE_SPECIFIC) { ALOGE("%s: failed: %s", __FUNCTION__, ret.getMessage()); if (ret.getExceptionCode() == EX_ILLEGAL_ARGUMENT) { return EINVAL; @@ -401,8 +723,10 @@ int APerformanceHintSession::setThreads(const int32_t* threadIds, size_t size) { std::scoped_lock lock(sHintMutex); traceThreads(tids); + bool tooManyThreads = + ret.getExceptionCode() == EX_SERVICE_SPECIFIC && ret.getServiceSpecificError() == 5; - return 0; + return tooManyThreads ? EBUSY : 0; } int APerformanceHintSession::getThreadIds(int32_t* const threadIds, size_t* size) { @@ -449,10 +773,16 @@ int APerformanceHintSession::reportActualWorkDuration(AWorkDuration* workDuratio int APerformanceHintSession::reportActualWorkDurationInternal(AWorkDuration* workDuration) { int64_t actualTotalDurationNanos = workDuration->durationNanos; - traceActualDuration(workDuration->durationNanos); int64_t now = uptimeNanos(); workDuration->timeStampNanos = now; std::scoped_lock lock(sHintMutex); + + if (mTargetDurationNanos <= 0) { + ALOGE("Cannot report work durations if the target duration is not positive."); + return EINVAL; + } + + traceActualDuration(actualTotalDurationNanos); mActualWorkDurations.push_back(std::move(*workDuration)); if (actualTotalDurationNanos >= mTargetDurationNanos) { @@ -495,6 +825,60 @@ int APerformanceHintSession::reportActualWorkDurationInternal(AWorkDuration* wor return 0; } +status_t APerformanceHintSession::setNativeSurfaces(ANativeWindow** windows, size_t numWindows, + ASurfaceControl** controls, + size_t numSurfaceControls) { + if (!mSessionConfig.has_value()) { + return ENOTSUP; + } + + std::vector<sp<IBinder>> layerHandles; + APerformanceHintManager::layersFromNativeSurfaces<sp<IBinder>>(windows, numWindows, controls, + numSurfaceControls, + layerHandles); + + std::vector<ndk::SpAIBinder> ndkLayerHandles; + for (auto&& handle : layerHandles) { + ndkLayerHandles.emplace_back(ndk::SpAIBinder(AIBinder_fromPlatformBinder(handle))); + } + + auto ret = mHintSession->associateToLayers(ndkLayerHandles); + if (!ret.isOk()) { + return EPIPE; + } + return 0; +} + +template <class T> +void APerformanceHintManager::layersFromNativeSurfaces(ANativeWindow** windows, int numWindows, + ASurfaceControl** controls, + int numSurfaceControls, + std::vector<T>& out) { + std::scoped_lock lock(sHintMutex); + if (windows != nullptr) { + std::vector<ANativeWindow*> windowVec(windows, windows + numWindows); + for (auto&& window : windowVec) { + Surface* surface = static_cast<Surface*>(window); + if (surface != nullptr) { + const sp<IBinder>& handle = surface->getSurfaceControlHandle(); + if (handle != nullptr) { + out.push_back(handle); + } + } + } + } + + if (controls != nullptr) { + std::vector<ASurfaceControl*> controlVec(controls, controls + numSurfaceControls); + for (auto&& aSurfaceControl : controlVec) { + SurfaceControl* control = reinterpret_cast<SurfaceControl*>(aSurfaceControl); + if (control->isValid()) { + out.push_back(control->getHandle()); + } + } + } +} + // ===================================== FMQ wrapper implementation bool FMQWrapper::isActive() { @@ -522,24 +906,32 @@ bool FMQWrapper::isSupported() { } bool FMQWrapper::startChannel(IHintManager* manager) { - if (isSupported() && !isActive()) { - std::optional<hal::ChannelConfig> config; - auto ret = manager->getSessionChannel(mToken, &config); - if (ret.isOk() && config.has_value()) { - std::scoped_lock lock{sHintMutex}; - mQueue = std::make_shared<HalMessageQueue>(config->channelDescriptor, true); - if (config->eventFlagDescriptor.has_value()) { - mFlagQueue = std::make_shared<HalFlagQueue>(*config->eventFlagDescriptor, true); - android::hardware::EventFlag::createEventFlag(mFlagQueue->getEventFlagWord(), - &mEventFlag); - mWriteMask = config->writeFlagBitmask; + if (isSupported() && !isActive() && manager->isRemote()) { + mChannelCreationFinished = std::async(std::launch::async, [&, this, manager]() { + std::optional<hal::ChannelConfig> config; + auto ret = manager->getSessionChannel(mToken, &config); + if (ret.isOk() && config.has_value()) { + std::scoped_lock lock{sHintMutex}; + mQueue = std::make_shared<HalMessageQueue>(config->channelDescriptor, true); + if (config->eventFlagDescriptor.has_value()) { + mFlagQueue = std::make_shared<HalFlagQueue>(*config->eventFlagDescriptor, true); + android::hardware::EventFlag::createEventFlag(mFlagQueue->getEventFlagWord(), + &mEventFlag); + mWriteMask = config->writeFlagBitmask; + } + updatePersistentTransaction(); + } else if (ret.isOk() && !config.has_value()) { + ALOGV("FMQ channel enabled but unsupported."); + setUnsupported(); + } else { + ALOGE("%s: FMQ channel initialization failed: %s", __FUNCTION__, ret.getMessage()); } - updatePersistentTransaction(); - } else if (ret.isOk() && !config.has_value()) { - ALOGV("FMQ channel enabled but unsupported."); - setUnsupported(); - } else { - ALOGE("%s: FMQ channel initialization failed: %s", __FUNCTION__, ret.getMessage()); + return true; + }); + + // If we're unit testing the FMQ, we should block for it to finish completing + if (gForceFMQEnabled.has_value()) { + mChannelCreationFinished.wait(); } } return isActive(); @@ -558,24 +950,25 @@ void FMQWrapper::stopChannel(IHintManager* manager) { } template <HalChannelMessageContents::Tag T, class C> -void FMQWrapper::writeBuffer(C* message, hal::SessionConfig& config, size_t) { - new (mFmqTransaction.getSlot(0)) hal::ChannelMessage{ - .sessionID = static_cast<int32_t>(config.id), - .timeStampNanos = ::android::uptimeNanos(), - .data = HalChannelMessageContents::make<T, C>(std::move(*message)), - }; +void FMQWrapper::writeBuffer(C* message, hal::SessionConfig& config, size_t count, int64_t now) { + for (size_t i = 0; i < count; ++i) { + new (mFmqTransaction.getSlot(i)) hal::ChannelMessage{ + .sessionID = static_cast<int32_t>(config.id), + .timeStampNanos = now, + .data = HalChannelMessageContents::make<T, C>(std::move(*(message + i))), + }; + } } template <> void FMQWrapper::writeBuffer<HalChannelMessageContents::workDuration>(hal::WorkDuration* messages, hal::SessionConfig& config, - size_t count) { + size_t count, int64_t now) { for (size_t i = 0; i < count; ++i) { hal::WorkDuration& message = messages[i]; new (mFmqTransaction.getSlot(i)) hal::ChannelMessage{ .sessionID = static_cast<int32_t>(config.id), - .timeStampNanos = - (i == count - 1) ? ::android::uptimeNanos() : message.timeStampNanos, + .timeStampNanos = (i == count - 1) ? now : message.timeStampNanos, .data = HalChannelMessageContents::make<HalChannelMessageContents::workDuration, hal::WorkDurationFixedV1>({ .durationNanos = message.cpuDurationNanos, @@ -588,7 +981,8 @@ void FMQWrapper::writeBuffer<HalChannelMessageContents::workDuration>(hal::WorkD } template <HalChannelMessageContents::Tag T, bool urgent, class C> -bool FMQWrapper::sendMessages(std::optional<hal::SessionConfig>& config, C* message, size_t count) { +bool FMQWrapper::sendMessages(std::optional<hal::SessionConfig>& config, C* message, size_t count, + int64_t now) { if (!isActiveLocked() || !config.has_value() || mCorrupted) { return false; } @@ -602,7 +996,7 @@ bool FMQWrapper::sendMessages(std::optional<hal::SessionConfig>& config, C* mess return false; } } - writeBuffer<T, C>(message, *config, count); + writeBuffer<T, C>(message, *config, count, now); mQueue->commitWrite(count); mEventFlag->wake(mWriteMask); // Re-create the persistent transaction after writing @@ -634,10 +1028,9 @@ bool FMQWrapper::updateTargetWorkDuration(std::optional<hal::SessionConfig>& con return sendMessages<HalChannelMessageContents::targetDuration>(config, &targetDurationNanos); } -bool FMQWrapper::sendHint(std::optional<hal::SessionConfig>& config, SessionHint hint) { - return sendMessages<HalChannelMessageContents::hint>(config, - reinterpret_cast<hal::SessionHint*>( - &hint)); +bool FMQWrapper::sendHints(std::optional<hal::SessionConfig>& config, + std::vector<hal::SessionHint>& hints, int64_t now) { + return sendMessages<HalChannelMessageContents::hint>(config, hints.data(), hints.size(), now); } bool FMQWrapper::setMode(std::optional<hal::SessionConfig>& config, hal::SessionMode mode, @@ -649,7 +1042,7 @@ bool FMQWrapper::setMode(std::optional<hal::SessionConfig>& config, hal::Session // ===================================== Tracing helpers -void APerformanceHintSession::traceThreads(std::vector<int32_t>& tids) { +void APerformanceHintSession::traceThreads(const std::vector<int32_t>& tids) { std::set<int32_t> tidSet{tids.begin(), tids.end()}; // Disable old TID tracing @@ -675,6 +1068,30 @@ void APerformanceHintSession::tracePowerEfficient(bool powerEfficient) { ATrace_setCounter((mSessionName + " power efficiency mode").c_str(), powerEfficient); } +void APerformanceHintSession::traceGraphicsPipeline(bool graphicsPipeline) { + ATrace_setCounter((mSessionName + " graphics pipeline mode").c_str(), graphicsPipeline); +} + +void APerformanceHintSession::traceModes(const std::vector<hal::SessionMode>& modesToEnable) { + // Iterate through all modes to trace, set to enable for all modes in modesToEnable, + // and set to disable for those are not. + for (hal::SessionMode mode : + {hal::SessionMode::POWER_EFFICIENCY, hal::SessionMode::GRAPHICS_PIPELINE}) { + bool isEnabled = + find(modesToEnable.begin(), modesToEnable.end(), mode) != modesToEnable.end(); + switch (mode) { + case hal::SessionMode::POWER_EFFICIENCY: + tracePowerEfficient(isEnabled); + break; + case hal::SessionMode::GRAPHICS_PIPELINE: + traceGraphicsPipeline(isEnabled); + break; + default: + break; + } + } +} + void APerformanceHintSession::traceActualDuration(int64_t actualDuration) { ATrace_setCounter((mSessionName + " actual duration").c_str(), actualDuration); } @@ -688,7 +1105,8 @@ void APerformanceHintSession::traceTargetDuration(int64_t targetDuration) { ATrace_setCounter((mSessionName + " target duration").c_str(), targetDuration); } -// ===================================== C API +// ===================================== Start of C API + APerformanceHintManager* APerformanceHint_getManager() { return APerformanceHintManager::getInstance(); } @@ -696,10 +1114,16 @@ APerformanceHintManager* APerformanceHint_getManager() { #define VALIDATE_PTR(ptr) \ LOG_ALWAYS_FATAL_IF(ptr == nullptr, "%s: " #ptr " is nullptr", __FUNCTION__); +#define HARD_VALIDATE_INT(value, cmp) \ + LOG_ALWAYS_FATAL_IF(!(value cmp), \ + "%s: Invalid value. Check failed: (" #value " " #cmp \ + ") with value: %" PRIi64, \ + __FUNCTION__, static_cast<int64_t>(value)); + #define VALIDATE_INT(value, cmp) \ if (!(value cmp)) { \ ALOGE("%s: Invalid value. Check failed: (" #value " " #cmp ") with value: %" PRIi64, \ - __FUNCTION__, value); \ + __FUNCTION__, static_cast<int64_t>(value)); \ return EINVAL; \ } @@ -717,6 +1141,30 @@ APerformanceHintSession* APerformanceHint_createSession(APerformanceHintManager* return manager->createSession(threadIds, size, initialTargetWorkDurationNanos); } +int APerformanceHint_createSessionUsingConfig(APerformanceHintManager* manager, + ASessionCreationConfig* sessionCreationConfig, + APerformanceHintSession** sessionOut) { + VALIDATE_PTR(manager); + VALIDATE_PTR(sessionCreationConfig); + VALIDATE_PTR(sessionOut); + *sessionOut = nullptr; + + return manager->createSessionUsingConfig(sessionCreationConfig, sessionOut); +} + +int APerformanceHint_createSessionUsingConfigInternal(APerformanceHintManager* manager, + ASessionCreationConfig* sessionCreationConfig, + APerformanceHintSession** sessionOut, + SessionTag tag) { + VALIDATE_PTR(manager); + VALIDATE_PTR(sessionCreationConfig); + VALIDATE_PTR(sessionOut); + *sessionOut = nullptr; + + return manager->createSessionUsingConfig(sessionCreationConfig, sessionOut, + static_cast<hal::SessionTag>(tag)); +} + APerformanceHintSession* APerformanceHint_createSessionInternal( APerformanceHintManager* manager, const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos, SessionTag tag) { @@ -726,14 +1174,35 @@ APerformanceHintSession* APerformanceHint_createSessionInternal( static_cast<hal::SessionTag>(tag)); } +APerformanceHintSession* APerformanceHint_createSessionFromJava( + APerformanceHintManager* manager, const int32_t* threadIds, size_t size, + int64_t initialTargetWorkDurationNanos) { + VALIDATE_PTR(manager) + VALIDATE_PTR(threadIds) + return manager->createSession(threadIds, size, initialTargetWorkDurationNanos, + hal::SessionTag::APP, true); +} + +APerformanceHintSession* APerformanceHint_borrowSessionFromJava(JNIEnv* env, jobject sessionObj) { + VALIDATE_PTR(env) + VALIDATE_PTR(sessionObj) + return APerformanceHintManager::getInstance()->getSessionFromJava(env, sessionObj); +} + int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* manager) { VALIDATE_PTR(manager) return manager->getPreferredRateNanos(); } +int APerformanceHint_getMaxGraphicsPipelineThreadsCount(APerformanceHintManager* manager) { + VALIDATE_PTR(manager); + return manager->getMaxGraphicsPipelineThreadsCount(); +} + int APerformanceHint_updateTargetWorkDuration(APerformanceHintSession* session, int64_t targetDurationNanos) { VALIDATE_PTR(session) + VALIDATE_INT(targetDurationNanos, >= 0) return session->updateTargetWorkDuration(targetDurationNanos); } @@ -746,12 +1215,24 @@ int APerformanceHint_reportActualWorkDuration(APerformanceHintSession* session, void APerformanceHint_closeSession(APerformanceHintSession* session) { VALIDATE_PTR(session) + if (session->isJava()) { + LOG_ALWAYS_FATAL("%s: Java-owned PerformanceHintSession cannot be closed in native", + __FUNCTION__); + return; + } + delete session; +} + +void APerformanceHint_closeSessionFromJava(APerformanceHintSession* session) { + VALIDATE_PTR(session) delete session; } int APerformanceHint_sendHint(APerformanceHintSession* session, SessionHint hint) { VALIDATE_PTR(session) - return session->sendHint(hint); + std::vector<hal::SessionHint> hints{static_cast<hal::SessionHint>(hint)}; + int64_t now = ::android::uptimeNanos(); + return session->sendHints(hints, now, "HWUI hint"); } int APerformanceHint_setThreads(APerformanceHintSession* session, const pid_t* threadIds, @@ -784,6 +1265,45 @@ int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session, return session->reportActualWorkDuration(workDurationPtr); } +int APerformanceHint_notifyWorkloadIncrease(APerformanceHintSession* session, bool cpu, bool gpu, + const char* debugName) { + VALIDATE_PTR(session) + VALIDATE_PTR(debugName) + return session->notifyWorkloadIncrease(cpu, gpu, debugName); +} + +int APerformanceHint_notifyWorkloadReset(APerformanceHintSession* session, bool cpu, bool gpu, + const char* debugName) { + VALIDATE_PTR(session) + VALIDATE_PTR(debugName) + return session->notifyWorkloadReset(cpu, gpu, debugName); +} + +int APerformanceHint_notifyWorkloadSpike(APerformanceHintSession* session, bool cpu, bool gpu, + const char* debugName) { + VALIDATE_PTR(session) + VALIDATE_PTR(debugName) + return session->notifyWorkloadSpike(cpu, gpu, debugName); +} + +int APerformanceHint_setNativeSurfaces(APerformanceHintSession* session, + ANativeWindow** nativeWindows, size_t nativeWindowsSize, + ASurfaceControl** surfaceControls, + size_t surfaceControlsSize) { + VALIDATE_PTR(session) + return session->setNativeSurfaces(nativeWindows, nativeWindowsSize, surfaceControls, + surfaceControlsSize); +} + +bool APerformanceHint_isFeatureSupported(APerformanceHintFeature feature) { + APerformanceHintManager* manager = APerformanceHintManager::getInstance(); + if (manager == nullptr) { + // Clearly whatever it is isn't supported in this case + return false; + } + return manager->isFeatureSupported(feature); +} + AWorkDuration* AWorkDuration_create() { return new AWorkDuration(); } @@ -831,3 +1351,66 @@ void APerformanceHint_setIHintManagerForTesting(void* iManager) { void APerformanceHint_setUseFMQForTesting(bool enabled) { gForceFMQEnabled = enabled; } + +ASessionCreationConfig* ASessionCreationConfig_create() { + return new ASessionCreationConfig(); +} + +void ASessionCreationConfig_release(ASessionCreationConfig* config) { + VALIDATE_PTR(config) + delete config; +} + +void ASessionCreationConfig_setTids(ASessionCreationConfig* config, const pid_t* tids, + size_t size) { + VALIDATE_PTR(config) + VALIDATE_PTR(tids) + HARD_VALIDATE_INT(size, > 0) + + config->tids = std::vector<int32_t>(tids, tids + size); +} + +void ASessionCreationConfig_setTargetWorkDurationNanos(ASessionCreationConfig* config, + int64_t targetWorkDurationNanos) { + VALIDATE_PTR(config) + config->targetWorkDurationNanos = targetWorkDurationNanos; +} + +void ASessionCreationConfig_setPreferPowerEfficiency(ASessionCreationConfig* config, bool enabled) { + VALIDATE_PTR(config) + config->setMode(hal::SessionMode::POWER_EFFICIENCY, enabled); +} + +void ASessionCreationConfig_setGraphicsPipeline(ASessionCreationConfig* config, bool enabled) { + VALIDATE_PTR(config) + config->setMode(hal::SessionMode::GRAPHICS_PIPELINE, enabled); +} + +void APerformanceHint_getRateLimiterPropertiesForTesting(int32_t* maxLoadHintsPerInterval, + int64_t* loadHintInterval) { + *maxLoadHintsPerInterval = kMaxLoadHintsPerInterval; + *loadHintInterval = kLoadHintInterval; +} + +void APerformanceHint_setUseNewLoadHintBehaviorForTesting(bool newBehavior) { + kForceNewHintBehavior = newBehavior; +} + +void ASessionCreationConfig_setNativeSurfaces(ASessionCreationConfig* config, + ANativeWindow** nativeWindows, + size_t nativeWindowsSize, + ASurfaceControl** surfaceControls, + size_t surfaceControlsSize) { + VALIDATE_PTR(config) + APerformanceHintManager::layersFromNativeSurfaces<wp<IBinder>>(nativeWindows, nativeWindowsSize, + surfaceControls, + surfaceControlsSize, + config->layers); +} + +void ASessionCreationConfig_setUseAutoTiming(ASessionCreationConfig* _Nonnull config, bool cpu, + bool gpu) { + VALIDATE_PTR(config) + config->setMode(hal::SessionMode::AUTO_CPU, cpu); + config->setMode(hal::SessionMode::AUTO_GPU, gpu); +} diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index e46db6bb3727..4fe0b80f3951 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -14,12 +14,15 @@ * limitations under the License. */ +#include <android/gui/LutProperties.h> #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h> #include <android/native_window.h> #include <android/surface_control.h> #include <android/surface_control_jni.h> #include <android_runtime/android_view_SurfaceControl.h> #include <configstore/Utils.h> +#include <cutils/ashmem.h> +#include <display_luts_private.h> #include <gui/HdrMetadata.h> #include <gui/ISurfaceComposer.h> #include <gui/Surface.h> @@ -53,6 +56,16 @@ static_assert(static_cast<int>(ADATASPACE_SCRGB) == static_cast<int>(HAL_DATASPA static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) == static_cast<int>(HAL_DATASPACE_DISPLAY_P3)); static_assert(static_cast<int>(ADATASPACE_BT2020_PQ) == static_cast<int>(HAL_DATASPACE_BT2020_PQ)); +static_assert(static_cast<int>(ADISPLAYLUTS_ONE_DIMENSION) == + static_cast<int>(android::gui::LutProperties::Dimension::ONE_D)); +static_assert(static_cast<int>(ADISPLAYLUTS_THREE_DIMENSION) == + static_cast<int>(android::gui::LutProperties::Dimension::THREE_D)); +static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_RGB) == + static_cast<int>(android::gui::LutProperties::SamplingKey::RGB)); +static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_MAX_RGB) == + static_cast<int>(android::gui::LutProperties::SamplingKey::MAX_RGB)); +static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_CIE_Y) == + static_cast<int>(android::gui::LutProperties::SamplingKey::CIE_Y)); Transaction* ASurfaceTransaction_to_Transaction(ASurfaceTransaction* aSurfaceTransaction) { return reinterpret_cast<Transaction*>(aSurfaceTransaction); @@ -693,6 +706,58 @@ void ASurfaceTransaction_setDesiredHdrHeadroom(ASurfaceTransaction* aSurfaceTran transaction->setDesiredHdrHeadroom(surfaceControl, desiredRatio); } +void ASurfaceTransaction_setLuts(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, + const struct ADisplayLuts* luts) { + CHECK_NOT_NULL(aSurfaceTransaction); + CHECK_NOT_NULL(aSurfaceControl); + + sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); + Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); + + int fd = -1; + std::vector<int32_t> offsets; + std::vector<int32_t> dimensions; + std::vector<int32_t> sizes; + std::vector<int32_t> samplingKeys; + + if (luts) { + std::vector<float> buffer(luts->totalBufferSize); + int32_t count = luts->offsets.size(); + offsets = luts->offsets; + + dimensions.reserve(count); + sizes.reserve(count); + samplingKeys.reserve(count); + for (int32_t i = 0; i < count; i++) { + dimensions.emplace_back(luts->entries[i]->properties.dimension); + sizes.emplace_back(luts->entries[i]->properties.size); + samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey); + std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(), + buffer.begin() + offsets[i]); + } + + // mmap + fd = ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float)); + if (fd < 0) { + LOG_ALWAYS_FATAL("setLuts, ashmem_create_region() failed"); + return; + } + void* ptr = mmap(nullptr, luts->totalBufferSize * sizeof(float), PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (ptr == MAP_FAILED) { + LOG_ALWAYS_FATAL("setLuts, Failed to map the shared memory"); + return; + } + + memcpy(ptr, buffer.data(), luts->totalBufferSize * sizeof(float)); + munmap(ptr, luts->totalBufferSize * sizeof(float)); + } + + transaction->setLuts(surfaceControl, base::unique_fd(fd), offsets, dimensions, sizes, + samplingKeys); +} + void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl, float r, float g, float b, float alpha, diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp index 91f78ce6f950..0c07b2acbb0c 100644 --- a/native/android/system_fonts.cpp +++ b/native/android/system_fonts.cpp @@ -327,7 +327,7 @@ AFont* _Nonnull AFontMatcher_match( result->mWeight = font->style().weight(); result->mItalic = font->style().slant() == minikin::FontStyle::Slant::ITALIC; result->mCollectionIndex = minikinFontSkia->GetFontIndex(); - const std::vector<minikin::FontVariation>& axes = minikinFontSkia->GetAxes(); + const minikin::VariationSettings& axes = minikinFontSkia->GetAxes(); result->mAxes.reserve(axes.size()); for (auto axis : axes) { result->mAxes.push_back(std::make_pair(axis.axisTag, axis.value)); diff --git a/native/android/system_health.cpp b/native/android/system_health.cpp new file mode 100644 index 000000000000..1b43e71c7bf0 --- /dev/null +++ b/native/android/system_health.cpp @@ -0,0 +1,371 @@ +/* + * 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 "system_health" + +#include <aidl/android/hardware/power/CpuHeadroomParams.h> +#include <aidl/android/hardware/power/GpuHeadroomParams.h> +#include <aidl/android/os/CpuHeadroomParamsInternal.h> +#include <aidl/android/os/GpuHeadroomParamsInternal.h> +#include <aidl/android/os/IHintManager.h> +#include <android/binder_manager.h> +#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; +namespace hal = aidl::android::hardware::power; + +struct ACpuHeadroomParams : public CpuHeadroomParamsInternal {}; +struct AGpuHeadroomParams : public GpuHeadroomParamsInternal {}; + +struct ASystemHealthManager { +public: + static ASystemHealthManager* getInstance(); + + ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager, + IHintManager::HintManagerClientData&& clientData); + ASystemHealthManager() = delete; + ~ASystemHealthManager(); + int getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom); + int getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom); + int getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis); + int getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis); + int getMaxCpuHeadroomTidsSize(size_t* outSize); + int getCpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis, + int32_t* _Nonnull outMaxMillis); + int getGpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis, + int32_t* _Nonnull outMaxMillis); + +private: + static ASystemHealthManager* create(std::shared_ptr<IHintManager> hintManager); + std::shared_ptr<IHintManager> mHintManager; + 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; +} + +ASystemHealthManager::ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager, + IHintManager::HintManagerClientData&& clientData) + : mHintManager(std::move(hintManager)), mClientData(clientData) {} + +ASystemHealthManager::~ASystemHealthManager() = default; + +ASystemHealthManager* ASystemHealthManager::create(std::shared_ptr<IHintManager> hintManager) { + if (!hintManager) { + hintManager = IHintManager::fromBinder( + ndk::SpAIBinder(AServiceManager_waitForService("performance_hint"))); + } + if (hintManager == nullptr) { + ALOGE("%s: PerformanceHint service is not ready ", __FUNCTION__); + return nullptr; + } + IHintManager::HintManagerClientData clientData; + ndk::ScopedAStatus ret = hintManager->getClientData(&clientData); + if (!ret.isOk()) { + ALOGE("%s: PerformanceHint service is not initialized %s", __FUNCTION__, ret.getMessage()); + return nullptr; + } + return new ASystemHealthManager(hintManager, std::move(clientData)); +} + +int ASystemHealthManager::getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom) { + if (!mClientData.supportInfo.headroom.isCpuSupported) return ENOTSUP; + std::optional<hal::CpuHeadroomResult> res; + ::ndk::ScopedAStatus ret; + CpuHeadroomParamsInternal internalParams; + if (!params) { + ret = mHintManager->getCpuHeadroom(internalParams, &res); + } else { + LOG_ALWAYS_FATAL_IF((int)params->tids.size() > mClientData.maxCpuHeadroomThreads, + "%s: tids size should not exceed %d", __FUNCTION__, + mClientData.maxCpuHeadroomThreads); + LOG_ALWAYS_FATAL_IF(params->calculationWindowMillis < + mClientData.supportInfo.headroom + .cpuMinCalculationWindowMillis || + params->calculationWindowMillis > + mClientData.supportInfo.headroom + .cpuMaxCalculationWindowMillis, + "%s: calculationWindowMillis should be in range [%d, %d] but got %d", + __FUNCTION__, + mClientData.supportInfo.headroom.cpuMinCalculationWindowMillis, + mClientData.supportInfo.headroom.cpuMaxCalculationWindowMillis, + params->calculationWindowMillis); + ret = mHintManager->getCpuHeadroom(*params, &res); + } + if (!ret.isOk()) { + LOG_ALWAYS_FATAL_IF(ret.getExceptionCode() == EX_ILLEGAL_ARGUMENT, + "Invalid ACpuHeadroomParams: %s", ret.getMessage()); + ALOGE("ASystemHealth_getCpuHeadroom fails: %s", ret.getMessage()); + if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { + return ENOTSUP; + } else if (ret.getExceptionCode() == EX_SECURITY) { + return EPERM; + } + return EPIPE; + } + *outHeadroom = res ? res->get<hal::CpuHeadroomResult::Tag::globalHeadroom>() + : std::numeric_limits<float>::quiet_NaN(); + return OK; +} + +int ASystemHealthManager::getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom) { + if (!mClientData.supportInfo.headroom.isGpuSupported) return ENOTSUP; + std::optional<hal::GpuHeadroomResult> res; + ::ndk::ScopedAStatus ret; + GpuHeadroomParamsInternal internalParams; + if (!params) { + ret = mHintManager->getGpuHeadroom(internalParams, &res); + } else { + LOG_ALWAYS_FATAL_IF(params->calculationWindowMillis < + mClientData.supportInfo.headroom + .gpuMinCalculationWindowMillis || + params->calculationWindowMillis > + mClientData.supportInfo.headroom + .gpuMaxCalculationWindowMillis, + "%s: calculationWindowMillis should be in range [%d, %d] but got %d", + __FUNCTION__, + mClientData.supportInfo.headroom.gpuMinCalculationWindowMillis, + mClientData.supportInfo.headroom.gpuMaxCalculationWindowMillis, + params->calculationWindowMillis); + ret = mHintManager->getGpuHeadroom(*params, &res); + } + if (!ret.isOk()) { + LOG_ALWAYS_FATAL_IF(ret.getExceptionCode() == EX_ILLEGAL_ARGUMENT, + "Invalid AGpuHeadroomParams: %s", ret.getMessage()); + ALOGE("ASystemHealth_getGpuHeadroom fails: %s", ret.getMessage()); + if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { + return ENOTSUP; + } + return EPIPE; + } + *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; + *outMinIntervalMillis = mClientData.supportInfo.headroom.cpuMinIntervalMillis; + return OK; +} + +int ASystemHealthManager::getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) { + if (!mClientData.supportInfo.headroom.isGpuSupported) return ENOTSUP; + *outMinIntervalMillis = mClientData.supportInfo.headroom.gpuMinIntervalMillis; + return OK; +} + +int ASystemHealthManager::getMaxCpuHeadroomTidsSize(size_t* outSize) { + if (!mClientData.supportInfo.headroom.isGpuSupported) return ENOTSUP; + *outSize = mClientData.maxCpuHeadroomThreads; + return OK; +} + +int ASystemHealthManager::getCpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis, + int32_t* _Nonnull outMaxMillis) { + if (!mClientData.supportInfo.headroom.isCpuSupported) return ENOTSUP; + *outMinMillis = mClientData.supportInfo.headroom.cpuMinCalculationWindowMillis; + *outMaxMillis = mClientData.supportInfo.headroom.cpuMaxCalculationWindowMillis; + return OK; +} + +int ASystemHealthManager::getGpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis, + int32_t* _Nonnull outMaxMillis) { + if (!mClientData.supportInfo.headroom.isGpuSupported) return ENOTSUP; + *outMinMillis = mClientData.supportInfo.headroom.gpuMinCalculationWindowMillis; + *outMaxMillis = mClientData.supportInfo.headroom.gpuMaxCalculationWindowMillis; + return OK; +} + +int ASystemHealth_getMaxCpuHeadroomTidsSize(size_t* _Nonnull outSize) { + LOG_ALWAYS_FATAL_IF(outSize == nullptr, "%s: outSize should not be null", __FUNCTION__); + auto manager = ASystemHealthManager::getInstance(); + if (manager == nullptr) return ENOTSUP; + return manager->getMaxCpuHeadroomTidsSize(outSize); +} + +int ASystemHealth_getCpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis, + int32_t* _Nonnull outMaxMillis) { + LOG_ALWAYS_FATAL_IF(outMinMillis == nullptr, "%s: outMinMillis should not be null", + __FUNCTION__); + LOG_ALWAYS_FATAL_IF(outMaxMillis == nullptr, "%s: outMaxMillis should not be null", + __FUNCTION__); + auto manager = ASystemHealthManager::getInstance(); + if (manager == nullptr) return ENOTSUP; + return manager->getCpuHeadroomCalculationWindowRange(outMinMillis, outMaxMillis); +} + +int ASystemHealth_getGpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis, + int32_t* _Nonnull outMaxMillis) { + LOG_ALWAYS_FATAL_IF(outMinMillis == nullptr, "%s: outMinMillis should not be null", + __FUNCTION__); + LOG_ALWAYS_FATAL_IF(outMaxMillis == nullptr, "%s: outMaxMillis should not be null", + __FUNCTION__); + auto manager = ASystemHealthManager::getInstance(); + if (manager == nullptr) return ENOTSUP; + return manager->getGpuHeadroomCalculationWindowRange(outMinMillis, outMaxMillis); +} + +int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams* _Nullable params, + float* _Nonnull outHeadroom) { + LOG_ALWAYS_FATAL_IF(outHeadroom == nullptr, "%s: outHeadroom should not be null", __FUNCTION__); + auto manager = ASystemHealthManager::getInstance(); + if (manager == nullptr) return ENOTSUP; + return manager->getCpuHeadroom(params, outHeadroom); +} + +int ASystemHealth_getGpuHeadroom(const AGpuHeadroomParams* _Nullable params, + float* _Nonnull outHeadroom) { + LOG_ALWAYS_FATAL_IF(outHeadroom == nullptr, "%s: outHeadroom should not be null", __FUNCTION__); + auto manager = ASystemHealthManager::getInstance(); + if (manager == nullptr) return ENOTSUP; + return manager->getGpuHeadroom(params, outHeadroom); +} + +int ASystemHealth_getCpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis) { + LOG_ALWAYS_FATAL_IF(outMinIntervalMillis == nullptr, + "%s: outMinIntervalMillis should not be null", __FUNCTION__); + auto manager = ASystemHealthManager::getInstance(); + if (manager == nullptr) return ENOTSUP; + return manager->getCpuHeadroomMinIntervalMillis(outMinIntervalMillis); +} + +int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis) { + LOG_ALWAYS_FATAL_IF(outMinIntervalMillis == nullptr, + "%s: outMinIntervalMillis should not be null", __FUNCTION__); + auto manager = ASystemHealthManager::getInstance(); + if (manager == nullptr) return ENOTSUP; + return manager->getGpuHeadroomMinIntervalMillis(outMinIntervalMillis); +} + +void ACpuHeadroomParams_setCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params, + int windowMillis) { + LOG_ALWAYS_FATAL_IF(windowMillis <= 0, "%s: windowMillis should be positive but got %d", + __FUNCTION__, windowMillis); + params->calculationWindowMillis = windowMillis; +} + +void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params, + int windowMillis) { + LOG_ALWAYS_FATAL_IF(windowMillis <= 0, "%s: windowMillis should be positive but got %d", + __FUNCTION__, windowMillis); + params->calculationWindowMillis = windowMillis; +} + +int ACpuHeadroomParams_getCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params) { + return params->calculationWindowMillis; +} + +int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params) { + return params->calculationWindowMillis; +} + +void ACpuHeadroomParams_setTids(ACpuHeadroomParams* _Nonnull params, const int* _Nonnull tids, + size_t tidsSize) { + LOG_ALWAYS_FATAL_IF(tids == nullptr, "%s: tids should not be null", __FUNCTION__); + params->tids.resize(tidsSize); + for (int i = 0; i < (int)tidsSize; ++i) { + LOG_ALWAYS_FATAL_IF(tids[i] <= 0, "ACpuHeadroomParams_setTids: Invalid non-positive tid %d", + tids[i]); + params->tids[i] = tids[i]; + } +} + +void ACpuHeadroomParams_setCalculationType(ACpuHeadroomParams* _Nonnull params, + ACpuHeadroomCalculationType calculationType) { + LOG_ALWAYS_FATAL_IF(calculationType < ACpuHeadroomCalculationType:: + ACPU_HEADROOM_CALCULATION_TYPE_MIN || + calculationType > ACpuHeadroomCalculationType:: + ACPU_HEADROOM_CALCULATION_TYPE_AVERAGE, + "%s: calculationType should be one of ACpuHeadroomCalculationType values " + "but got %d", + __FUNCTION__, calculationType); + params->calculationType = static_cast<hal::CpuHeadroomParams::CalculationType>(calculationType); +} + +ACpuHeadroomCalculationType ACpuHeadroomParams_getCalculationType( + ACpuHeadroomParams* _Nonnull params) { + return static_cast<ACpuHeadroomCalculationType>(params->calculationType); +} + +void AGpuHeadroomParams_setCalculationType(AGpuHeadroomParams* _Nonnull params, + AGpuHeadroomCalculationType calculationType) { + LOG_ALWAYS_FATAL_IF(calculationType < AGpuHeadroomCalculationType:: + AGPU_HEADROOM_CALCULATION_TYPE_MIN || + calculationType > AGpuHeadroomCalculationType:: + AGPU_HEADROOM_CALCULATION_TYPE_AVERAGE, + "%s: calculationType should be one of AGpuHeadroomCalculationType values " + "but got %d", + __FUNCTION__, calculationType); + params->calculationType = static_cast<hal::GpuHeadroomParams::CalculationType>(calculationType); +} + +AGpuHeadroomCalculationType AGpuHeadroomParams_getCalculationType( + AGpuHeadroomParams* _Nonnull params) { + return static_cast<AGpuHeadroomCalculationType>(params->calculationType); +} + +ACpuHeadroomParams* _Nonnull ACpuHeadroomParams_create() { + return new ACpuHeadroomParams(); +} + +AGpuHeadroomParams* _Nonnull AGpuHeadroomParams_create() { + return new AGpuHeadroomParams(); +} + +void ACpuHeadroomParams_destroy(ACpuHeadroomParams* _Nullable params) { + delete params; +} + +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/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index 9de3a6f525e6..0fa92eab4c0a 100644 --- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp @@ -18,9 +18,11 @@ #include <aidl/android/hardware/power/ChannelConfig.h> #include <aidl/android/hardware/power/SessionConfig.h> +#include <aidl/android/hardware/power/SessionMode.h> #include <aidl/android/hardware/power/SessionTag.h> #include <aidl/android/hardware/power/WorkDuration.h> #include <aidl/android/os/IHintManager.h> +#include <aidl/android/os/SessionCreationConfig.h> #include <android/binder_manager.h> #include <android/binder_status.h> #include <android/performance_hint.h> @@ -36,6 +38,7 @@ using namespace std::chrono_literals; namespace hal = aidl::android::hardware::power; using aidl::android::os::IHintManager; using aidl::android::os::IHintSession; +using aidl::android::os::SessionCreationConfig; using ndk::ScopedAStatus; using ndk::SpAIBinder; using HalChannelMessageContents = hal::ChannelMessage::ChannelMessageContents; @@ -46,14 +49,88 @@ using HalFlagQueue = ::android::AidlMessageQueue<int8_t, SynchronizedReadWrite>; using namespace android; using namespace testing; +constexpr int64_t DEFAULT_TARGET_NS = 16666666L; + +template <class T, void (*D)(T*)> +std::shared_ptr<T> wrapSP(T* incoming) { + return incoming == nullptr ? nullptr : std::shared_ptr<T>(incoming, [](T* ptr) { D(ptr); }); +} +constexpr auto&& wrapSession = wrapSP<APerformanceHintSession, APerformanceHint_closeSession>; +constexpr auto&& wrapConfig = wrapSP<ASessionCreationConfig, ASessionCreationConfig_release>; +constexpr auto&& wrapWorkDuration = wrapSP<AWorkDuration, AWorkDuration_release>; + +std::shared_ptr<ASessionCreationConfig> createConfig() { + return wrapConfig(ASessionCreationConfig_create()); +} + +struct ConfigCreator { + std::vector<int32_t> tids{1, 2}; + int64_t targetDuration = DEFAULT_TARGET_NS; + bool powerEfficient = false; + bool graphicsPipeline = false; + std::vector<ANativeWindow*> nativeWindows{}; + std::vector<ASurfaceControl*> surfaceControls{}; + bool autoCpu = false; + bool autoGpu = false; +}; + +struct SupportHelper { + bool hintSessions : 1; + bool powerEfficiency : 1; + bool bindToSurface : 1; + bool graphicsPipeline : 1; + bool autoCpu : 1; + bool autoGpu : 1; +}; + +SupportHelper getSupportHelper() { + return { + .hintSessions = APerformanceHint_isFeatureSupported(APERF_HINT_SESSIONS), + .powerEfficiency = APerformanceHint_isFeatureSupported(APERF_HINT_POWER_EFFICIENCY), + .bindToSurface = APerformanceHint_isFeatureSupported(APERF_HINT_SURFACE_BINDING), + .graphicsPipeline = APerformanceHint_isFeatureSupported(APERF_HINT_GRAPHICS_PIPELINE), + .autoCpu = APerformanceHint_isFeatureSupported(APERF_HINT_AUTO_CPU), + .autoGpu = APerformanceHint_isFeatureSupported(APERF_HINT_AUTO_GPU), + }; +} + +SupportHelper getFullySupportedSupportHelper() { + return { + .hintSessions = true, + .powerEfficiency = true, + .graphicsPipeline = true, + .autoCpu = true, + .autoGpu = true, + }; +} + +std::shared_ptr<ASessionCreationConfig> configFromCreator(ConfigCreator&& creator) { + auto config = createConfig(); + + ASessionCreationConfig_setTids(config.get(), creator.tids.data(), creator.tids.size()); + ASessionCreationConfig_setTargetWorkDurationNanos(config.get(), creator.targetDuration); + ASessionCreationConfig_setPreferPowerEfficiency(config.get(), creator.powerEfficient); + ASessionCreationConfig_setGraphicsPipeline(config.get(), creator.graphicsPipeline); + ASessionCreationConfig_setNativeSurfaces(config.get(), + creator.nativeWindows.size() > 0 + ? creator.nativeWindows.data() + : nullptr, + creator.nativeWindows.size(), + creator.surfaceControls.size() > 0 + ? creator.surfaceControls.data() + : nullptr, + creator.surfaceControls.size()); + ASessionCreationConfig_setUseAutoTiming(config.get(), creator.autoCpu, creator.autoGpu); + return config; +} + class MockIHintManager : public IHintManager { public: MOCK_METHOD(ScopedAStatus, createHintSessionWithConfig, - (const SpAIBinder& token, const ::std::vector<int32_t>& tids, int64_t durationNanos, - hal::SessionTag tag, hal::SessionConfig* config, - std::shared_ptr<IHintSession>* _aidl_return), + (const SpAIBinder& token, hal::SessionTag tag, + const SessionCreationConfig& creationConfig, hal::SessionConfig* config, + IHintManager::SessionCreationReturn* _aidl_return), (override)); - MOCK_METHOD(ScopedAStatus, getHintSessionPreferredRate, (int64_t * _aidl_return), (override)); MOCK_METHOD(ScopedAStatus, setHintSessionThreads, (const std::shared_ptr<IHintSession>& hintSession, const ::std::vector<int32_t>& tids), @@ -66,6 +143,27 @@ public: std::optional<hal::ChannelConfig>* _aidl_return), (override)); MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override)); + MOCK_METHOD(ScopedAStatus, getCpuHeadroom, + (const ::aidl::android::os::CpuHeadroomParamsInternal& in_params, + std::optional<hal::CpuHeadroomResult>* _aidl_return), + (override)); + MOCK_METHOD(ScopedAStatus, getCpuHeadroomMinIntervalMillis, (int64_t* _aidl_return), + (override)); + MOCK_METHOD(ScopedAStatus, getGpuHeadroom, + (const ::aidl::android::os::GpuHeadroomParamsInternal& in_params, + 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>& + clientDataIn, + ::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)); }; @@ -82,6 +180,8 @@ public: MOCK_METHOD(ScopedAStatus, close, (), (override)); MOCK_METHOD(ScopedAStatus, reportActualWorkDuration2, (const ::std::vector<hal::WorkDuration>& workDurations), (override)); + MOCK_METHOD(ScopedAStatus, associateToLayers, + (const std::vector<::ndk::SpAIBinder>& in_layerTokens), (override)); MOCK_METHOD(SpAIBinder, asBinder, (), (override)); MOCK_METHOD(bool, isRemote, (), (override)); }; @@ -90,7 +190,12 @@ class PerformanceHintTest : public Test { public: void SetUp() override { mMockIHintManager = ndk::SharedRefBase::make<NiceMock<MockIHintManager>>(); + APerformanceHint_getRateLimiterPropertiesForTesting(&mMaxLoadHintsPerInterval, + &mLoadHintInterval); APerformanceHint_setIHintManagerForTesting(&mMockIHintManager); + APerformanceHint_setUseNewLoadHintBehaviorForTesting(true); + mTids.push_back(1); + mTids.push_back(2); } void TearDown() override { @@ -101,23 +206,25 @@ public: APerformanceHintManager* createManager() { APerformanceHint_setUseFMQForTesting(mUsingFMQ); - ON_CALL(*mMockIHintManager, getHintSessionPreferredRate(_)) - .WillByDefault(DoAll(SetArgPointee<0>(123L), [] { return ScopedAStatus::ok(); })); + ON_CALL(*mMockIHintManager, registerClient(_, _)) + .WillByDefault( + DoAll(SetArgPointee<1>(mClientData), [] { return ScopedAStatus::ok(); })); + ON_CALL(*mMockIHintManager, isRemote()).WillByDefault(Return(true)); return APerformanceHint_getManager(); } - APerformanceHintSession* createSession(APerformanceHintManager* manager, - int64_t targetDuration = 56789L, bool isHwui = false) { + void prepareSessionMock() { mMockSession = ndk::SharedRefBase::make<NiceMock<MockIHintSession>>(); - int64_t sessionId = 123; - std::vector<int32_t> tids; - tids.push_back(1); - tids.push_back(2); - - ON_CALL(*mMockIHintManager, - createHintSessionWithConfig(_, Eq(tids), Eq(targetDuration), _, _, _)) - .WillByDefault(DoAll(SetArgPointee<4>(hal::SessionConfig({.id = sessionId})), - SetArgPointee<5>(std::shared_ptr<IHintSession>(mMockSession)), + const int64_t sessionId = 123; + + mSessionCreationReturn = IHintManager::SessionCreationReturn{ + .session = mMockSession, + .pipelineThreadLimitExceeded = false, + }; + + ON_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _)) + .WillByDefault(DoAll(SetArgPointee<3>(hal::SessionConfig({.id = sessionId})), + SetArgPointee<4>(mSessionCreationReturn), [] { return ScopedAStatus::ok(); })); ON_CALL(*mMockIHintManager, setHintSessionThreads(_, _)).WillByDefault([] { @@ -135,11 +242,36 @@ public: ON_CALL(*mMockSession, reportActualWorkDuration2(_)).WillByDefault([] { return ScopedAStatus::ok(); }); + } + + std::shared_ptr<APerformanceHintSession> createSession(APerformanceHintManager* manager, + int64_t targetDuration = 56789L, + bool isHwui = false) { + prepareSessionMock(); if (isHwui) { - return APerformanceHint_createSessionInternal(manager, tids.data(), tids.size(), - targetDuration, SessionTag::HWUI); + return wrapSession(APerformanceHint_createSessionInternal(manager, mTids.data(), + mTids.size(), targetDuration, + SessionTag::HWUI)); } - return APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration); + return wrapSession(APerformanceHint_createSession(manager, mTids.data(), mTids.size(), + targetDuration)); + } + + std::shared_ptr<APerformanceHintSession> createSessionUsingConfig( + APerformanceHintManager* manager, std::shared_ptr<ASessionCreationConfig>& config, + bool isHwui = false) { + prepareSessionMock(); + APerformanceHintSession* session; + int out = 0; + if (isHwui) { + out = APerformanceHint_createSessionUsingConfigInternal(manager, config.get(), &session, + SessionTag::HWUI); + } + + out = APerformanceHint_createSessionUsingConfig(manager, config.get(), &session); + EXPECT_EQ(out, 0); + + return wrapSession(session); } void setFMQEnabled(bool enabled) { @@ -170,11 +302,30 @@ public: uint32_t mWriteBits = 0x00000002; std::shared_ptr<NiceMock<MockIHintManager>> mMockIHintManager = nullptr; std::shared_ptr<NiceMock<MockIHintSession>> mMockSession = nullptr; + IHintManager::SessionCreationReturn mSessionCreationReturn; std::shared_ptr<AidlMessageQueue<hal::ChannelMessage, SynchronizedReadWrite>> mMockFMQ; std::shared_ptr<AidlMessageQueue<int8_t, SynchronizedReadWrite>> mMockFlagQueue; hardware::EventFlag* mEventFlag; int kMockQueueSize = 20; bool mUsingFMQ = false; + std::vector<int> mTids; + + IHintManager::HintManagerClientData mClientData{ + .powerHalVersion = 6, + .maxGraphicsPipelineThreads = 5, + .preferredRateNanos = 123L, + .supportInfo{ + .usesSessions = true, + .boosts = 0, + .modes = 0, + .sessionHints = -1, + .sessionModes = -1, + .sessionTags = -1, + }, + }; + + int32_t mMaxLoadHintsPerInterval; + int64_t mLoadHintInterval; template <HalChannelMessageContents::Tag T, class C = HalChannelMessageContents::_at<T>> void expectToReadFromFmq(C expected) { @@ -191,92 +342,111 @@ bool equalsWithoutTimestamp(hal::WorkDuration lhs, hal::WorkDuration rhs) { lhs.gpuDurationNanos == rhs.gpuDurationNanos && lhs.durationNanos == rhs.durationNanos; } -TEST_F(PerformanceHintTest, TestGetPreferredUpdateRateNanos) { - APerformanceHintManager* manager = createManager(); - int64_t preferredUpdateRateNanos = APerformanceHint_getPreferredUpdateRateNanos(manager); - EXPECT_EQ(123L, preferredUpdateRateNanos); -} - TEST_F(PerformanceHintTest, TestSession) { APerformanceHintManager* manager = createManager(); - APerformanceHintSession* session = createSession(manager); + auto&& session = createSession(manager); ASSERT_TRUE(session); int64_t targetDurationNanos = 10; EXPECT_CALL(*mMockSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1)); - int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos); + int result = APerformanceHint_updateTargetWorkDuration(session.get(), targetDurationNanos); EXPECT_EQ(0, result); // subsequent call with same target should be ignored but return no error - result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos); + result = APerformanceHint_updateTargetWorkDuration(session.get(), targetDurationNanos); EXPECT_EQ(0, result); + Mock::VerifyAndClearExpectations(mMockSession.get()); + usleep(2); // Sleep for longer than preferredUpdateRateNanos. int64_t actualDurationNanos = 20; std::vector<int64_t> actualDurations; actualDurations.push_back(20); EXPECT_CALL(*mMockSession, reportActualWorkDuration2(_)).Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos); + EXPECT_CALL(*mMockSession, updateTargetWorkDuration(_)).Times(Exactly(1)); + result = APerformanceHint_reportActualWorkDuration(session.get(), actualDurationNanos); EXPECT_EQ(0, result); - - result = APerformanceHint_updateTargetWorkDuration(session, -1L); + result = APerformanceHint_reportActualWorkDuration(session.get(), -1L); + EXPECT_EQ(EINVAL, result); + result = APerformanceHint_updateTargetWorkDuration(session.get(), 0); + EXPECT_EQ(0, result); + result = APerformanceHint_updateTargetWorkDuration(session.get(), -2); EXPECT_EQ(EINVAL, result); - result = APerformanceHint_reportActualWorkDuration(session, -1L); + result = APerformanceHint_reportActualWorkDuration(session.get(), 12L); EXPECT_EQ(EINVAL, result); SessionHint hintId = SessionHint::CPU_LOAD_RESET; EXPECT_CALL(*mMockSession, sendHint(Eq(hintId))).Times(Exactly(1)); - result = APerformanceHint_sendHint(session, hintId); + result = APerformanceHint_sendHint(session.get(), hintId); EXPECT_EQ(0, result); - usleep(110000); // Sleep for longer than the update timeout. - EXPECT_CALL(*mMockSession, sendHint(Eq(hintId))).Times(Exactly(1)); - result = APerformanceHint_sendHint(session, hintId); + EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::CPU_LOAD_UP))).Times(Exactly(1)); + result = APerformanceHint_notifyWorkloadIncrease(session.get(), true, false, "Test hint"); EXPECT_EQ(0, result); - // Expect to get rate limited if we try to send faster than the limiter allows - EXPECT_CALL(*mMockSession, sendHint(Eq(hintId))).Times(Exactly(0)); - result = APerformanceHint_sendHint(session, hintId); + EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::CPU_LOAD_RESET))).Times(Exactly(1)); + EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::GPU_LOAD_RESET))).Times(Exactly(1)); + result = APerformanceHint_notifyWorkloadReset(session.get(), true, true, "Test hint"); + EXPECT_EQ(0, result); + EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::CPU_LOAD_SPIKE))).Times(Exactly(1)); + EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::GPU_LOAD_SPIKE))).Times(Exactly(1)); + result = APerformanceHint_notifyWorkloadSpike(session.get(), true, true, "Test hint"); EXPECT_EQ(0, result); - result = APerformanceHint_sendHint(session, static_cast<SessionHint>(-1)); - EXPECT_EQ(EINVAL, result); + EXPECT_DEATH( + { APerformanceHint_sendHint(session.get(), static_cast<SessionHint>(-1)); }, + "invalid session hint"); + Mock::VerifyAndClearExpectations(mMockSession.get()); + for (int i = 0; i < mMaxLoadHintsPerInterval; ++i) { + APerformanceHint_sendHint(session.get(), hintId); + } + + // Expect to get rate limited if we try to send faster than the limiter allows + EXPECT_CALL(*mMockSession, sendHint(_)).Times(Exactly(0)); + result = APerformanceHint_notifyWorkloadIncrease(session.get(), true, true, "Test hint"); + EXPECT_EQ(result, EBUSY); + EXPECT_CALL(*mMockSession, sendHint(_)).Times(Exactly(0)); + result = APerformanceHint_notifyWorkloadReset(session.get(), true, true, "Test hint"); EXPECT_CALL(*mMockSession, close()).Times(Exactly(1)); - APerformanceHint_closeSession(session); } TEST_F(PerformanceHintTest, TestUpdatedSessionCreation) { - EXPECT_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _, _)).Times(1); + EXPECT_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _)).Times(1); APerformanceHintManager* manager = createManager(); - APerformanceHintSession* session = createSession(manager); + auto&& session = createSession(manager); + ASSERT_TRUE(session); +} + +TEST_F(PerformanceHintTest, TestSessionCreationUsingConfig) { + EXPECT_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _)).Times(1); + auto&& config = configFromCreator({.tids = mTids}); + APerformanceHintManager* manager = createManager(); + auto&& session = createSessionUsingConfig(manager, config); ASSERT_TRUE(session); - APerformanceHint_closeSession(session); } TEST_F(PerformanceHintTest, TestHwuiSessionCreation) { - EXPECT_CALL(*mMockIHintManager, - createHintSessionWithConfig(_, _, _, hal::SessionTag::HWUI, _, _)) + EXPECT_CALL(*mMockIHintManager, createHintSessionWithConfig(_, hal::SessionTag::HWUI, _, _, _)) .Times(1); APerformanceHintManager* manager = createManager(); - APerformanceHintSession* session = createSession(manager, 56789L, true); + auto&& session = createSession(manager, 56789L, true); ASSERT_TRUE(session); - APerformanceHint_closeSession(session); } TEST_F(PerformanceHintTest, SetThreads) { APerformanceHintManager* manager = createManager(); - APerformanceHintSession* session = createSession(manager); + auto&& session = createSession(manager); ASSERT_TRUE(session); int32_t emptyTids[2]; - int result = APerformanceHint_setThreads(session, emptyTids, 0); + int result = APerformanceHint_setThreads(session.get(), emptyTids, 0); EXPECT_EQ(EINVAL, result); std::vector<int32_t> newTids; newTids.push_back(1); newTids.push_back(3); EXPECT_CALL(*mMockIHintManager, setHintSessionThreads(_, Eq(newTids))).Times(Exactly(1)); - result = APerformanceHint_setThreads(session, newTids.data(), newTids.size()); + result = APerformanceHint_setThreads(session.get(), newTids.data(), newTids.size()); EXPECT_EQ(0, result); testing::Mock::VerifyAndClearExpectations(mMockIHintManager.get()); @@ -286,27 +456,27 @@ TEST_F(PerformanceHintTest, SetThreads) { EXPECT_CALL(*mMockIHintManager, setHintSessionThreads(_, Eq(invalidTids))) .Times(Exactly(1)) .WillOnce(Return(ByMove(ScopedAStatus::fromExceptionCode(EX_SECURITY)))); - result = APerformanceHint_setThreads(session, invalidTids.data(), invalidTids.size()); + result = APerformanceHint_setThreads(session.get(), invalidTids.data(), invalidTids.size()); EXPECT_EQ(EPERM, result); } TEST_F(PerformanceHintTest, SetPowerEfficient) { APerformanceHintManager* manager = createManager(); - APerformanceHintSession* session = createSession(manager); + auto&& session = createSession(manager); ASSERT_TRUE(session); EXPECT_CALL(*mMockSession, setMode(_, Eq(true))).Times(Exactly(1)); - int result = APerformanceHint_setPreferPowerEfficiency(session, true); + int result = APerformanceHint_setPreferPowerEfficiency(session.get(), true); EXPECT_EQ(0, result); EXPECT_CALL(*mMockSession, setMode(_, Eq(false))).Times(Exactly(1)); - result = APerformanceHint_setPreferPowerEfficiency(session, false); + result = APerformanceHint_setPreferPowerEfficiency(session.get(), false); EXPECT_EQ(0, result); } TEST_F(PerformanceHintTest, CreateZeroTargetDurationSession) { APerformanceHintManager* manager = createManager(); - APerformanceHintSession* session = createSession(manager, 0); + auto&& session = createSession(manager, 0); ASSERT_TRUE(session); } @@ -331,12 +501,12 @@ MATCHER_P(WorkDurationEq, expected, "") { TEST_F(PerformanceHintTest, TestAPerformanceHint_reportActualWorkDuration2) { APerformanceHintManager* manager = createManager(); - APerformanceHintSession* session = createSession(manager); + auto&& session = createSession(manager); ASSERT_TRUE(session); int64_t targetDurationNanos = 10; EXPECT_CALL(*mMockSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1)); - int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos); + int result = APerformanceHint_updateTargetWorkDuration(session.get(), targetDurationNanos); EXPECT_EQ(0, result); usleep(2); // Sleep for longer than preferredUpdateRateNanos. @@ -355,54 +525,53 @@ TEST_F(PerformanceHintTest, TestAPerformanceHint_reportActualWorkDuration2) { EXPECT_CALL(*mMockSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) .Times(Exactly(pair.expectedResult == OK)); - result = APerformanceHint_reportActualWorkDuration2(session, + result = APerformanceHint_reportActualWorkDuration2(session.get(), reinterpret_cast<AWorkDuration*>( &pair.duration)); EXPECT_EQ(pair.expectedResult, result); } EXPECT_CALL(*mMockSession, close()).Times(Exactly(1)); - APerformanceHint_closeSession(session); } TEST_F(PerformanceHintTest, TestAWorkDuration) { - AWorkDuration* aWorkDuration = AWorkDuration_create(); + // AWorkDuration* aWorkDuration = AWorkDuration_create(); + auto&& aWorkDuration = wrapWorkDuration(AWorkDuration_create()); ASSERT_NE(aWorkDuration, nullptr); - AWorkDuration_setWorkPeriodStartTimestampNanos(aWorkDuration, 1); - AWorkDuration_setActualTotalDurationNanos(aWorkDuration, 20); - AWorkDuration_setActualCpuDurationNanos(aWorkDuration, 13); - AWorkDuration_setActualGpuDurationNanos(aWorkDuration, 8); - AWorkDuration_release(aWorkDuration); + AWorkDuration_setWorkPeriodStartTimestampNanos(aWorkDuration.get(), 1); + AWorkDuration_setActualTotalDurationNanos(aWorkDuration.get(), 20); + AWorkDuration_setActualCpuDurationNanos(aWorkDuration.get(), 13); + AWorkDuration_setActualGpuDurationNanos(aWorkDuration.get(), 8); } TEST_F(PerformanceHintTest, TestCreateUsingFMQ) { setFMQEnabled(true); APerformanceHintManager* manager = createManager(); - APerformanceHintSession* session = createSession(manager); + auto&& session = createSession(manager); ASSERT_TRUE(session); } TEST_F(PerformanceHintTest, TestUpdateTargetWorkDurationUsingFMQ) { setFMQEnabled(true); APerformanceHintManager* manager = createManager(); - APerformanceHintSession* session = createSession(manager); - APerformanceHint_updateTargetWorkDuration(session, 456); + auto&& session = createSession(manager); + APerformanceHint_updateTargetWorkDuration(session.get(), 456); expectToReadFromFmq<HalChannelMessageContents::Tag::targetDuration>(456); } TEST_F(PerformanceHintTest, TestSendHintUsingFMQ) { setFMQEnabled(true); APerformanceHintManager* manager = createManager(); - APerformanceHintSession* session = createSession(manager); - APerformanceHint_sendHint(session, SessionHint::CPU_LOAD_UP); + auto&& session = createSession(manager); + APerformanceHint_sendHint(session.get(), SessionHint::CPU_LOAD_UP); expectToReadFromFmq<HalChannelMessageContents::Tag::hint>(hal::SessionHint::CPU_LOAD_UP); } TEST_F(PerformanceHintTest, TestReportActualUsingFMQ) { setFMQEnabled(true); APerformanceHintManager* manager = createManager(); - APerformanceHintSession* session = createSession(manager); + auto&& session = createSession(manager); hal::WorkDuration duration{.timeStampNanos = 3, .durationNanos = 999999, .workPeriodStartTimestampNanos = 1, @@ -416,7 +585,100 @@ TEST_F(PerformanceHintTest, TestReportActualUsingFMQ) { .gpuDurationNanos = duration.gpuDurationNanos, }; - APerformanceHint_reportActualWorkDuration2(session, + APerformanceHint_reportActualWorkDuration2(session.get(), reinterpret_cast<AWorkDuration*>(&duration)); expectToReadFromFmq<HalChannelMessageContents::Tag::workDuration>(durationExpected); } + +TEST_F(PerformanceHintTest, TestASessionCreationConfig) { + auto&& config = configFromCreator({ + .tids = mTids, + .targetDuration = 20, + .powerEfficient = true, + .graphicsPipeline = true, + }); + + APerformanceHintManager* manager = createManager(); + auto&& session = createSessionUsingConfig(manager, config); + + ASSERT_NE(session, nullptr); + ASSERT_NE(config, nullptr); +} + +TEST_F(PerformanceHintTest, TestSessionCreationWithNullLayers) { + EXPECT_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _)).Times(1); + auto&& config = configFromCreator( + {.tids = mTids, .nativeWindows = {nullptr}, .surfaceControls = {nullptr}}); + APerformanceHintManager* manager = createManager(); + auto&& session = createSessionUsingConfig(manager, config); + ASSERT_TRUE(session); +} + +TEST_F(PerformanceHintTest, TestSupportObject) { + // Disable GPU and Power Efficiency support to test partial enabling + mClientData.supportInfo.sessionModes &= ~(1 << (int)hal::SessionMode::AUTO_GPU); + mClientData.supportInfo.sessionHints &= ~(1 << (int)hal::SessionHint::GPU_LOAD_UP); + mClientData.supportInfo.sessionHints &= ~(1 << (int)hal::SessionHint::POWER_EFFICIENCY); + + APerformanceHintManager* manager = createManager(); + + union { + int expectedSupportInt; + SupportHelper expectedSupport; + }; + + union { + int actualSupportInt; + SupportHelper actualSupport; + }; + + expectedSupport = getFullySupportedSupportHelper(); + actualSupport = getSupportHelper(); + + expectedSupport.autoGpu = false; + + EXPECT_EQ(expectedSupportInt, actualSupportInt); +} + +TEST_F(PerformanceHintTest, TestCreatingAutoSession) { + // Disable GPU capability for testing + mClientData.supportInfo.sessionModes &= ~(1 << (int)hal::SessionMode::AUTO_GPU); + APerformanceHintManager* manager = createManager(); + + auto&& invalidConfig = configFromCreator({ + .tids = mTids, + .targetDuration = 20, + .graphicsPipeline = false, + .autoCpu = true, + .autoGpu = true, + }); + + EXPECT_DEATH({ createSessionUsingConfig(manager, invalidConfig); }, ""); + + auto&& unsupportedConfig = configFromCreator({ + .tids = mTids, + .targetDuration = 20, + .graphicsPipeline = true, + .autoCpu = true, + .autoGpu = true, + }); + + APerformanceHintSession* unsupportedSession = nullptr; + + // Creating a session with auto timing but no graphics pipeline should fail + int out = APerformanceHint_createSessionUsingConfig(manager, unsupportedConfig.get(), + &unsupportedSession); + EXPECT_EQ(out, ENOTSUP); + EXPECT_EQ(wrapSession(unsupportedSession), nullptr); + + auto&& validConfig = configFromCreator({ + .tids = mTids, + .targetDuration = 20, + .graphicsPipeline = true, + .autoCpu = true, + .autoGpu = false, + }); + + auto&& validSession = createSessionUsingConfig(manager, validConfig); + EXPECT_NE(validSession, nullptr); +} diff --git a/native/android/tests/system_health/Android.bp b/native/android/tests/system_health/Android.bp new file mode 100644 index 000000000000..30aeb77375ad --- /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 000000000000..3f08fc66e392 --- /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); +} diff --git a/native/android/tests/thermal/NativeThermalUnitTest.cpp b/native/android/tests/thermal/NativeThermalUnitTest.cpp index 6d6861a3026a..923ad011de41 100644 --- a/native/android/tests/thermal/NativeThermalUnitTest.cpp +++ b/native/android/tests/thermal/NativeThermalUnitTest.cpp @@ -67,14 +67,72 @@ public: MOCK_METHOD(Status, getThermalHeadroomThresholds, (::std::vector<float> * _aidl_return), (override)); MOCK_METHOD(IBinder*, onAsBinder, (), (override)); + MOCK_METHOD(Status, registerThermalHeadroomListener, + (const ::android::sp<::android::os::IThermalHeadroomListener>& listener, + bool* _aidl_return), + (override)); + MOCK_METHOD(Status, unregisterThermalHeadroomListener, + (const ::android::sp<::android::os::IThermalHeadroomListener>& listener, + bool* _aidl_return), + (override)); }; +struct HeadroomCallbackData { + void* data; + float headroom; + float forecast; + int32_t forecastSeconds; + const std::vector<float> thresholds; +}; + +struct StatusCallbackData { + void* data; + AThermalStatus status; +}; + +static std::optional<HeadroomCallbackData> headroomCalled1; +static std::optional<HeadroomCallbackData> headroomCalled2; +static std::optional<StatusCallbackData> statusCalled1; +static std::optional<StatusCallbackData> statusCalled2; + +static std::vector<float> convertThresholds(const AThermalHeadroomThreshold* thresholds, + size_t size) { + std::vector<float> ret; + for (int i = 0; i < (int)size; i++) { + ret.emplace_back(thresholds[i].headroom); + } + return ret; +}; + +static void onHeadroomChange1(void* data, float headroom, float forecast, int32_t forecastSeconds, + const AThermalHeadroomThreshold* thresholds, size_t size) { + headroomCalled1.emplace(data, headroom, forecast, forecastSeconds, + convertThresholds(thresholds, size)); +} + +static void onHeadroomChange2(void* data, float headroom, float forecast, int32_t forecastSeconds, + const AThermalHeadroomThreshold* thresholds, size_t size) { + headroomCalled2.emplace(data, headroom, forecast, forecastSeconds, + convertThresholds(thresholds, size)); +} + +static void onStatusChange1(void* data, AThermalStatus status) { + statusCalled1.emplace(data, status); +} +static void onStatusChange2(void* data, AThermalStatus status) { + statusCalled2.emplace(data, status); +} + class NativeThermalUnitTest : public Test { public: void SetUp() override { mMockIThermalService = new StrictMock<MockIThermalService>(); AThermal_setIThermalServiceForTesting(mMockIThermalService); mThermalManager = AThermal_acquireManager(); + headroomCalled1.reset(); + headroomCalled2.reset(); + statusCalled1.reset(); + statusCalled2.reset(); } void TearDown() override { @@ -109,9 +167,11 @@ TEST_F(NativeThermalUnitTest, TestGetThermalHeadroomThresholds) { 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); - + // following calls should not be cached + expected = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; + EXPECT_CALL(*mMockIThermalService, getThermalHeadroomThresholds(_)) + .Times(Exactly(1)) + .WillRepeatedly(DoAll(SetArgPointee<0>(expected), Return(Status()))); const AThermalHeadroomThreshold* thresholds2 = nullptr; size_t size2; ASSERT_EQ(OK, AThermal_getThermalHeadroomThresholds(mThermalManager, &thresholds2, &size2)); @@ -156,3 +216,248 @@ TEST_F(NativeThermalUnitTest, TestGetThermalHeadroomThresholdsFailedWithNonEmpty ASSERT_EQ(EINVAL, AThermal_getThermalHeadroomThresholds(mThermalManager, &initialized, &size)); delete[] initialized; } + +TEST_F(NativeThermalUnitTest, TestRegisterThermalHeadroomListener) { + EXPECT_CALL(*mMockIThermalService, registerThermalHeadroomListener(_, _)) + .Times(Exactly(2)) + .WillOnce(Return( + Status::fromExceptionCode(binder::Status::Exception::EX_TRANSACTION_FAILED))); + float data1 = 1.0f; + float data2 = 2.0f; + ASSERT_EQ(EPIPE, + AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1)); + ASSERT_EQ(EPIPE, + AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange2, &data2)); + + // verify only 1 service call to register a global listener + sp<IThermalHeadroomListener> capturedServiceListener; + Mock::VerifyAndClearExpectations(mMockIThermalService); + EXPECT_CALL(*mMockIThermalService, registerThermalHeadroomListener(_, _)) + .Times(Exactly(1)) + .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener), + testing::Invoke([](const sp<IThermalHeadroomListener>&, + bool* aidl_return) { *aidl_return = true; }), + Return(Status::ok()))); + ASSERT_EQ(0, + AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1)); + ASSERT_EQ(EINVAL, + AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1)); + ASSERT_EQ(0, + AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange2, &data2)); + const ::std::vector<float> thresholds = {0.1f, 0.2f}; + capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, thresholds); + ASSERT_TRUE(headroomCalled1.has_value()); + EXPECT_EQ(headroomCalled1->data, &data1); + EXPECT_EQ(headroomCalled1->headroom, 0.1f); + EXPECT_EQ(headroomCalled1->forecast, 0.3f); + EXPECT_EQ(headroomCalled1->forecastSeconds, 20); + EXPECT_EQ(headroomCalled1->thresholds, thresholds); + ASSERT_TRUE(headroomCalled2.has_value()); + EXPECT_EQ(headroomCalled2->data, &data2); + EXPECT_EQ(headroomCalled2->headroom, 0.1f); + EXPECT_EQ(headroomCalled2->forecast, 0.3f); + EXPECT_EQ(headroomCalled2->forecastSeconds, 20); + EXPECT_EQ(headroomCalled2->thresholds, thresholds); + + // after test finished the global service listener should be unregistered + EXPECT_CALL(*mMockIThermalService, unregisterThermalHeadroomListener(_, _)) + .Times(Exactly(1)) + .WillOnce(Return(binder::Status::ok())); +} + +TEST_F(NativeThermalUnitTest, TestUnregisterThermalHeadroomListener) { + sp<IThermalHeadroomListener> capturedServiceListener; + EXPECT_CALL(*mMockIThermalService, registerThermalHeadroomListener(_, _)) + .Times(Exactly(1)) + .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener), + testing::Invoke([](const sp<IThermalHeadroomListener>&, + bool* aidl_return) { *aidl_return = true; }), + Return(Status::ok()))); + float data1 = 1.0f; + float data2 = 2.0f; + ASSERT_EQ(0, + AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1)); + ASSERT_EQ(0, + AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange2, &data2)); + capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, {}); + ASSERT_TRUE(headroomCalled1.has_value()); + ASSERT_TRUE(headroomCalled2.has_value()); + + EXPECT_CALL(*mMockIThermalService, unregisterThermalHeadroomListener(_, _)) + .Times(Exactly(1)) + .WillRepeatedly(Return( + Status::fromExceptionCode(binder::Status::Exception::EX_TRANSACTION_FAILED))); + + // callback 1 should be unregistered and callback 2 unregistration should fail due to service + // listener unregistration call failure + ASSERT_EQ(0, + AThermal_unregisterThermalHeadroomListener(mThermalManager, onHeadroomChange1, + &data1)); + ASSERT_EQ(EPIPE, + AThermal_unregisterThermalHeadroomListener(mThermalManager, onHeadroomChange2, + &data2)); + // verify only callback 2 is called after callback 1 is unregistered + std::vector<float> thresholds = {0.1f, 0.2f}; + headroomCalled1.reset(); + headroomCalled2.reset(); + capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, thresholds); + ASSERT_TRUE(!headroomCalled1.has_value()); + ASSERT_TRUE(headroomCalled2.has_value()); + + // verify only 1 service call to unregister global service listener + Mock::VerifyAndClearExpectations(mMockIThermalService); + EXPECT_CALL(*mMockIThermalService, unregisterThermalHeadroomListener(_, _)) + .Times(Exactly(1)) + .WillOnce(DoAll(testing::Invoke([](const sp<IThermalHeadroomListener>&, + bool* aidl_return) { *aidl_return = true; }), + Return(Status::ok()))); + ASSERT_EQ(EINVAL, + AThermal_unregisterThermalHeadroomListener(mThermalManager, onHeadroomChange1, + &data1)); + ASSERT_EQ(0, + AThermal_unregisterThermalHeadroomListener(mThermalManager, onHeadroomChange2, + &data2)); + // verify neither callback is called after global service listener is unregistered + headroomCalled1.reset(); + headroomCalled2.reset(); + capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, thresholds); + ASSERT_TRUE(!headroomCalled1.has_value()); + ASSERT_TRUE(!headroomCalled2.has_value()); + + // verify adding a new callback will still work + Mock::VerifyAndClearExpectations(mMockIThermalService); + EXPECT_CALL(*mMockIThermalService, registerThermalHeadroomListener(_, _)) + .Times(Exactly(1)) + .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener), + testing::Invoke([](const sp<IThermalHeadroomListener>&, + bool* aidl_return) { *aidl_return = true; }), + Return(Status::ok()))); + ASSERT_EQ(0, + AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1)); + headroomCalled1.reset(); + capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, thresholds); + ASSERT_TRUE(headroomCalled1.has_value()); + EXPECT_EQ(headroomCalled1->data, &data1); + EXPECT_EQ(headroomCalled1->headroom, 0.1f); + EXPECT_EQ(headroomCalled1->forecast, 0.3f); + EXPECT_EQ(headroomCalled1->forecastSeconds, 20); + EXPECT_EQ(headroomCalled1->thresholds, thresholds); + + // after test finished the global service listener should be unregistered + EXPECT_CALL(*mMockIThermalService, unregisterThermalHeadroomListener(_, _)) + .Times(Exactly(1)) + .WillOnce(Return(binder::Status::ok())); +} + +TEST_F(NativeThermalUnitTest, TestRegisterThermalStatusListener) { + EXPECT_CALL(*mMockIThermalService, registerThermalStatusListener(_, _)) + .Times(Exactly(2)) + .WillOnce(Return( + Status::fromExceptionCode(binder::Status::Exception::EX_TRANSACTION_FAILED))); + int data1 = 1; + int data2 = 2; + ASSERT_EQ(EPIPE, + AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1)); + ASSERT_EQ(EPIPE, + AThermal_registerThermalStatusListener(mThermalManager, onStatusChange2, &data2)); + + // verify only 1 service call to register a global listener + sp<IThermalStatusListener> capturedServiceListener; + Mock::VerifyAndClearExpectations(mMockIThermalService); + EXPECT_CALL(*mMockIThermalService, registerThermalStatusListener(_, _)) + .Times(Exactly(1)) + .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener), + testing::Invoke([](const sp<IThermalStatusListener>&, + bool* aidl_return) { *aidl_return = true; }), + Return(Status::ok()))); + ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1)); + ASSERT_EQ(EINVAL, + AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1)); + ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange2, &data2)); + + capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT); + ASSERT_TRUE(statusCalled1.has_value()); + EXPECT_EQ(statusCalled1->data, &data1); + EXPECT_EQ(statusCalled1->status, AThermalStatus::ATHERMAL_STATUS_LIGHT); + ASSERT_TRUE(statusCalled2.has_value()); + EXPECT_EQ(statusCalled2->data, &data2); + EXPECT_EQ(statusCalled2->status, AThermalStatus::ATHERMAL_STATUS_LIGHT); + + // after test finished the callback should be unregistered + EXPECT_CALL(*mMockIThermalService, unregisterThermalStatusListener(_, _)) + .Times(Exactly(1)) + .WillOnce(Return(binder::Status::ok())); +} + +TEST_F(NativeThermalUnitTest, TestUnregisterThermalStatusListener) { + sp<IThermalStatusListener> capturedServiceListener; + EXPECT_CALL(*mMockIThermalService, registerThermalStatusListener(_, _)) + .Times(Exactly(1)) + .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener), + testing::Invoke([](const sp<IThermalStatusListener>&, + bool* aidl_return) { *aidl_return = true; }), + Return(Status::ok()))); + int data1 = 1; + int data2 = 2; + ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1)); + ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange2, &data2)); + capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT); + ASSERT_TRUE(statusCalled1.has_value()); + ASSERT_TRUE(statusCalled2.has_value()); + + EXPECT_CALL(*mMockIThermalService, unregisterThermalStatusListener(_, _)) + .Times(Exactly(1)) + .WillOnce(Return( + Status::fromExceptionCode(binder::Status::Exception::EX_TRANSACTION_FAILED))); + // callback 1 should be unregistered and callback 2 unregistration should fail due to service + // listener unregistration call failure + ASSERT_EQ(0, + AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange1, &data1)); + ASSERT_EQ(EPIPE, + AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange2, &data2)); + + // verify only callback 2 is called after callback 1 is unregistered + statusCalled1.reset(); + statusCalled2.reset(); + capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT); + ASSERT_TRUE(!statusCalled1.has_value()); + ASSERT_TRUE(statusCalled2.has_value()); + + // verify only 1 service call to unregister global service listener + Mock::VerifyAndClearExpectations(mMockIThermalService); + EXPECT_CALL(*mMockIThermalService, unregisterThermalStatusListener(_, _)) + .Times(Exactly(1)) + .WillOnce(DoAll(testing::Invoke([](const sp<IThermalStatusListener>&, + bool* aidl_return) { *aidl_return = true; }), + Return(Status::ok()))); + ASSERT_EQ(EINVAL, + AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange1, &data1)); + ASSERT_EQ(0, + AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange2, &data2)); + // verify neither callback is called after global service listener is unregistered + statusCalled1.reset(); + statusCalled2.reset(); + capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT); + ASSERT_TRUE(!statusCalled1.has_value()); + ASSERT_TRUE(!statusCalled2.has_value()); + + // verify adding a new callback will still work + Mock::VerifyAndClearExpectations(mMockIThermalService); + EXPECT_CALL(*mMockIThermalService, registerThermalStatusListener(_, _)) + .Times(Exactly(1)) + .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener), + testing::Invoke([](const sp<IThermalStatusListener>&, + bool* aidl_return) { *aidl_return = true; }), + Return(Status::ok()))); + ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1)); + statusCalled1.reset(); + capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT); + ASSERT_TRUE(statusCalled1.has_value()); + EXPECT_EQ(statusCalled1->data, &data1); + EXPECT_EQ(statusCalled1->status, AThermalStatus::ATHERMAL_STATUS_LIGHT); + + // after test finished the global service listener should be unregistered + EXPECT_CALL(*mMockIThermalService, unregisterThermalStatusListener(_, _)) + .Times(Exactly(1)) + .WillOnce(Return(binder::Status::ok())); +} diff --git a/native/android/thermal.cpp b/native/android/thermal.cpp index f7a3537d3f4a..cefcaf7766bb 100644 --- a/native/android/thermal.cpp +++ b/native/android/thermal.cpp @@ -17,6 +17,7 @@ #define LOG_TAG "thermal" #include <android-base/thread_annotations.h> +#include <android/os/BnThermalHeadroomListener.h> #include <android/os/BnThermalStatusListener.h> #include <android/os/IThermalService.h> #include <android/thermal.h> @@ -33,10 +34,10 @@ using android::sp; using namespace android; using namespace android::os; -struct ThermalServiceListener : public BnThermalStatusListener { +struct ThermalServiceStatusListener : public BnThermalStatusListener { public: virtual binder::Status onStatusChange(int32_t status) override; - ThermalServiceListener(AThermalManager *manager) { + ThermalServiceStatusListener(AThermalManager *manager) { mMgr = manager; } @@ -44,11 +45,29 @@ private: AThermalManager *mMgr; }; -struct ListenerCallback { +struct ThermalServiceHeadroomListener : public BnThermalHeadroomListener { +public: + virtual binder::Status onHeadroomChange(float headroom, float forecastHeadroom, + int32_t forecastSeconds, + const ::std::vector<float> &thresholds) override; + ThermalServiceHeadroomListener(AThermalManager *manager) { + mMgr = manager; + } + +private: + AThermalManager *mMgr; +}; + +struct StatusListenerCallback { AThermal_StatusCallback callback; void* data; }; +struct HeadroomListenerCallback { + AThermal_HeadroomCallback callback; + void *data; +}; + static IThermalService *gIThermalServiceForTesting = nullptr; struct AThermalManager { @@ -57,30 +76,44 @@ public: AThermalManager() = delete; ~AThermalManager(); status_t notifyStateChange(int32_t status); + status_t notifyHeadroomChange(float headroom, float forecastHeadroom, int32_t forecastSeconds, + const ::std::vector<float> &thresholds); status_t getCurrentThermalStatus(int32_t *status); - status_t addListener(AThermal_StatusCallback, void *data); - status_t removeListener(AThermal_StatusCallback, void *data); + status_t addStatusListener(AThermal_StatusCallback, void *data); + status_t removeStatusListener(AThermal_StatusCallback, void *data); status_t getThermalHeadroom(int32_t forecastSeconds, float *result); status_t getThermalHeadroomThresholds(const AThermalHeadroomThreshold **, size_t *size); + status_t addHeadroomListener(AThermal_HeadroomCallback, void *data); + status_t removeHeadroomListener(AThermal_HeadroomCallback, void *data); 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); + std::mutex mStatusListenerMutex; + sp<ThermalServiceStatusListener> mServiceStatusListener GUARDED_BY(mStatusListenerMutex); + std::vector<StatusListenerCallback> mStatusListeners GUARDED_BY(mStatusListenerMutex); + + std::mutex mHeadroomListenerMutex; + sp<ThermalServiceHeadroomListener> mServiceHeadroomListener GUARDED_BY(mHeadroomListenerMutex); + std::vector<HeadroomListenerCallback> mHeadroomListeners GUARDED_BY(mHeadroomListenerMutex); }; -binder::Status ThermalServiceListener::onStatusChange(int32_t status) { +binder::Status ThermalServiceStatusListener::onStatusChange(int32_t status) { if (mMgr != nullptr) { mMgr->notifyStateChange(status); } return binder::Status::ok(); } +binder::Status ThermalServiceHeadroomListener::onHeadroomChange( + float headroom, float forecastHeadroom, int32_t forecastSeconds, + const ::std::vector<float> &thresholds) { + if (mMgr != nullptr) { + mMgr->notifyHeadroomChange(headroom, forecastHeadroom, forecastSeconds, thresholds); + } + return binder::Status::ok(); +} + AThermalManager* AThermalManager::createAThermalManager() { if (gIThermalServiceForTesting) { return new AThermalManager(gIThermalServiceForTesting); @@ -96,97 +129,183 @@ AThermalManager* AThermalManager::createAThermalManager() { } AThermalManager::AThermalManager(sp<IThermalService> service) - : mThermalSvc(std::move(service)), mServiceListener(nullptr) {} + : mThermalSvc(std::move(service)), + mServiceStatusListener(nullptr), + mServiceHeadroomListener(nullptr) {} AThermalManager::~AThermalManager() { { - std::scoped_lock<std::mutex> listenerLock(mListenerMutex); - mListeners.clear(); - if (mServiceListener != nullptr) { + std::scoped_lock<std::mutex> listenerLock(mStatusListenerMutex); + mStatusListeners.clear(); + if (mServiceStatusListener != nullptr) { bool success = false; - mThermalSvc->unregisterThermalStatusListener(mServiceListener, &success); - mServiceListener = nullptr; + mThermalSvc->unregisterThermalStatusListener(mServiceStatusListener, &success); + mServiceStatusListener = nullptr; + } + } + { + std::scoped_lock<std::mutex> headroomListenerLock(mHeadroomListenerMutex); + mHeadroomListeners.clear(); + if (mServiceHeadroomListener != nullptr) { + bool success = false; + mThermalSvc->unregisterThermalHeadroomListener(mServiceHeadroomListener, &success); + mServiceHeadroomListener = nullptr; } } - std::scoped_lock<std::mutex> lock(mThresholdsMutex); - delete[] mThresholds; } status_t AThermalManager::notifyStateChange(int32_t status) { - std::scoped_lock<std::mutex> lock(mListenerMutex); + std::scoped_lock<std::mutex> lock(mStatusListenerMutex); AThermalStatus thermalStatus = static_cast<AThermalStatus>(status); - for (auto listener : mListeners) { + for (auto listener : mStatusListeners) { listener.callback(listener.data, thermalStatus); } return OK; } -status_t AThermalManager::addListener(AThermal_StatusCallback callback, void *data) { - std::scoped_lock<std::mutex> lock(mListenerMutex); +status_t AThermalManager::notifyHeadroomChange(float headroom, float forecastHeadroom, + int32_t forecastSeconds, + const ::std::vector<float> &thresholds) { + std::scoped_lock<std::mutex> lock(mHeadroomListenerMutex); + size_t thresholdsCount = thresholds.size(); + auto t = new AThermalHeadroomThreshold[thresholdsCount]; + for (int i = 0; i < (int)thresholdsCount; i++) { + t[i].headroom = thresholds[i]; + t[i].thermalStatus = static_cast<AThermalStatus>(i); + } + for (auto listener : mHeadroomListeners) { + listener.callback(listener.data, headroom, forecastHeadroom, forecastSeconds, t, + thresholdsCount); + } + delete[] t; + return OK; +} + +status_t AThermalManager::addStatusListener(AThermal_StatusCallback callback, void *data) { + std::scoped_lock<std::mutex> lock(mStatusListenerMutex); if (callback == nullptr) { // Callback can not be nullptr return EINVAL; } - for (const auto& cb : mListeners) { + for (const auto &cb : mStatusListeners) { // Don't re-add callbacks. if (callback == cb.callback && data == cb.data) { return EINVAL; } } - mListeners.emplace_back(ListenerCallback{callback, data}); - if (mServiceListener != nullptr) { + if (mServiceStatusListener != nullptr) { + mStatusListeners.emplace_back(StatusListenerCallback{callback, data}); return OK; } bool success = false; - mServiceListener = new ThermalServiceListener(this); - if (mServiceListener == nullptr) { + mServiceStatusListener = new ThermalServiceStatusListener(this); + if (mServiceStatusListener == nullptr) { return ENOMEM; } - auto ret = mThermalSvc->registerThermalStatusListener(mServiceListener, &success); + auto ret = mThermalSvc->registerThermalStatusListener(mServiceStatusListener, &success); if (!success || !ret.isOk()) { + mServiceStatusListener = nullptr; ALOGE("Failed in registerThermalStatusListener %d", success); if (ret.exceptionCode() == binder::Status::EX_SECURITY) { return EPERM; } return EPIPE; } + mStatusListeners.emplace_back(StatusListenerCallback{callback, data}); return OK; } -status_t AThermalManager::removeListener(AThermal_StatusCallback callback, void *data) { - std::scoped_lock<std::mutex> lock(mListenerMutex); +status_t AThermalManager::removeStatusListener(AThermal_StatusCallback callback, void *data) { + std::scoped_lock<std::mutex> lock(mStatusListenerMutex); - auto it = std::remove_if(mListeners.begin(), - mListeners.end(), - [&](const ListenerCallback& cb) { - return callback == cb.callback && - data == cb.data; + auto it = std::remove_if(mStatusListeners.begin(), mStatusListeners.end(), + [&](const StatusListenerCallback &cb) { + return callback == cb.callback && data == cb.data; }); - if (it == mListeners.end()) { + if (it == mStatusListeners.end()) { // If the listener and data pointer were not previously added. return EINVAL; } - mListeners.erase(it, mListeners.end()); + if (mServiceStatusListener == nullptr || mStatusListeners.size() > 1) { + mStatusListeners.erase(it, mStatusListeners.end()); + return OK; + } - if (!mListeners.empty()) { + bool success = false; + auto ret = mThermalSvc->unregisterThermalStatusListener(mServiceStatusListener, &success); + if (!success || !ret.isOk()) { + ALOGE("Failed in unregisterThermalStatusListener %d", success); + if (ret.exceptionCode() == binder::Status::EX_SECURITY) { + return EPERM; + } + return EPIPE; + } + mServiceStatusListener = nullptr; + mStatusListeners.erase(it, mStatusListeners.end()); + return OK; +} + +status_t AThermalManager::addHeadroomListener(AThermal_HeadroomCallback callback, void *data) { + std::scoped_lock<std::mutex> lock(mHeadroomListenerMutex); + if (callback == nullptr) { + return EINVAL; + } + for (const auto &cb : mHeadroomListeners) { + if (callback == cb.callback && data == cb.data) { + return EINVAL; + } + } + + if (mServiceHeadroomListener != nullptr) { + mHeadroomListeners.emplace_back(HeadroomListenerCallback{callback, data}); return OK; } - if (mServiceListener == nullptr) { + bool success = false; + mServiceHeadroomListener = new ThermalServiceHeadroomListener(this); + if (mServiceHeadroomListener == nullptr) { + return ENOMEM; + } + auto ret = mThermalSvc->registerThermalHeadroomListener(mServiceHeadroomListener, &success); + if (!success || !ret.isOk()) { + ALOGE("Failed in registerThermalHeadroomListener %d", success); + mServiceHeadroomListener = nullptr; + if (ret.exceptionCode() == binder::Status::EX_SECURITY) { + return EPERM; + } + return EPIPE; + } + mHeadroomListeners.emplace_back(HeadroomListenerCallback{callback, data}); + return OK; +} + +status_t AThermalManager::removeHeadroomListener(AThermal_HeadroomCallback callback, void *data) { + std::scoped_lock<std::mutex> lock(mHeadroomListenerMutex); + + auto it = std::remove_if(mHeadroomListeners.begin(), mHeadroomListeners.end(), + [&](const HeadroomListenerCallback &cb) { + return callback == cb.callback && data == cb.data; + }); + if (it == mHeadroomListeners.end()) { + return EINVAL; + } + if (mServiceHeadroomListener == nullptr || mHeadroomListeners.size() > 1) { + mHeadroomListeners.erase(it, mHeadroomListeners.end()); return OK; } bool success = false; - auto ret = mThermalSvc->unregisterThermalStatusListener(mServiceListener, &success); + auto ret = mThermalSvc->unregisterThermalHeadroomListener(mServiceHeadroomListener, &success); if (!success || !ret.isOk()) { - ALOGE("Failed in unregisterThermalStatusListener %d", success); + ALOGE("Failed in unregisterThermalHeadroomListener %d", success); if (ret.exceptionCode() == binder::Status::EX_SECURITY) { return EPERM; } return EPIPE; } - mServiceListener = nullptr; + mServiceHeadroomListener = nullptr; + mHeadroomListeners.erase(it, mHeadroomListeners.end()); return OK; } @@ -216,61 +335,36 @@ status_t AThermalManager::getThermalHeadroom(int32_t forecastSeconds, float *res status_t AThermalManager::getThermalHeadroomThresholds(const AThermalHeadroomThreshold **result, size_t *size) { - std::scoped_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); + 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; } - mThresholds = t; + return EPIPE; + } + size_t thresholdsCount = thresholds->size(); + auto t = new AThermalHeadroomThreshold[thresholdsCount]; + for (int i = 0; i < (int)thresholdsCount; i++) { + t[i].headroom = (*thresholds)[i]; + t[i].thermalStatus = static_cast<AThermalStatus>(i); } - *size = mThresholdsCount; - *result = mThresholds; + *size = thresholdsCount; + *result = t; return OK; } -/** - * Acquire an instance of the thermal manager. This must be freed using - * {@link AThermal_releaseManager}. - * - * @return manager instance on success, nullptr on failure. - */ AThermalManager* AThermal_acquireManager() { auto manager = AThermalManager::createAThermalManager(); return manager; } -/** - * Release the thermal manager pointer acquired by - * {@link AThermal_acquireManager}. - * - * @param manager The manager to be released. - * - */ void AThermal_releaseManager(AThermalManager *manager) { delete manager; } -/** - * Gets the current thermal status. - * - * @param manager The manager instance to use to query the thermal status, - * acquired by {@link AThermal_acquireManager}. - * - * @return current thermal status, ATHERMAL_STATUS_ERROR on failure. -*/ AThermalStatus AThermal_getCurrentThermalStatus(AThermalManager *manager) { int32_t status = 0; status_t ret = manager->getCurrentThermalStatus(&status); @@ -280,59 +374,16 @@ AThermalStatus AThermal_getCurrentThermalStatus(AThermalManager *manager) { return static_cast<AThermalStatus>(status); } -/** - * Register the thermal status listener for thermal status change. - * - * @param manager The manager instance to use to register. - * acquired by {@link AThermal_acquireManager}. - * @param callback The callback function to be called when thermal status updated. - * @param data The data pointer to be passed when callback is called. - * - * @return 0 on success - * EINVAL if the listener and data pointer were previously added and not removed. - * EPERM if the required permission is not held. - * EPIPE if communication with the system service has failed. - */ int AThermal_registerThermalStatusListener(AThermalManager *manager, - AThermal_StatusCallback callback, void *data) { - return manager->addListener(callback, data); + AThermal_StatusCallback callback, void *data) { + return manager->addStatusListener(callback, data); } -/** - * Unregister the thermal status listener previously resgistered. - * - * @param manager The manager instance to use to unregister. - * acquired by {@link AThermal_acquireManager}. - * @param callback The callback function to be called when thermal status updated. - * @param data The data pointer to be passed when callback is called. - * - * @return 0 on success - * EINVAL if the listener and data pointer were not previously added. - * EPERM if the required permission is not held. - * EPIPE if communication with the system service has failed. - */ int AThermal_unregisterThermalStatusListener(AThermalManager *manager, - AThermal_StatusCallback callback, void *data) { - return manager->removeListener(callback, data); + AThermal_StatusCallback callback, void *data) { + return manager->removeStatusListener(callback, data); } -/** - * Provides an estimate of how much thermal headroom the device currently has - * before hitting severe throttling. - * - * Note that this only attempts to track the headroom of slow-moving sensors, - * such as the skin temperature sensor. This means that there is no benefit to - * calling this function more frequently than about once per second, and attempts - * to call significantly more frequently may result in the function returning {@code NaN}. - * - * See also PowerManager#getThermalHeadroom. - * - * @param manager The manager instance to use - * @param forecastSeconds how many seconds in the future to forecast - * @return a value greater than or equal to 0.0 where 1.0 indicates the SEVERE throttling - * 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 result = 0.0f; status_t ret = manager->getThermalHeadroom(forecastSeconds, &result); @@ -354,3 +405,13 @@ int AThermal_getThermalHeadroomThresholds(AThermalManager *manager, void AThermal_setIThermalServiceForTesting(void *iThermalService) { gIThermalServiceForTesting = static_cast<IThermalService *>(iThermalService); } + +int AThermal_registerThermalHeadroomListener(AThermalManager *manager, + AThermal_HeadroomCallback callback, void *data) { + return manager->addHeadroomListener(callback, data); +} + +int AThermal_unregisterThermalHeadroomListener(AThermalManager *manager, + AThermal_HeadroomCallback callback, void *data) { + return manager->removeHeadroomListener(callback, data); +} diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp index fdd44de67a37..bc273c2d0833 100644 --- a/native/graphics/jni/Android.bp +++ b/native/graphics/jni/Android.bp @@ -50,7 +50,9 @@ cc_library_shared { ], export_header_lib_headers: ["native_headers"], - static_libs: ["libarect"], + static_libs: [ + "libarect", + ], host_supported: true, target: { @@ -62,6 +64,11 @@ cc_library_shared { shared_libs: [ "libandroid", ], + static_libs: [ + "libstatslog_hwui", + "libstatspull_lazy", + "libstatssocket_lazy", + ], version_script: "libjnigraphics.map.txt", }, host: { diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp index e18b4a9d2420..cb95bcf5d2b1 100644 --- a/native/graphics/jni/imagedecoder.cpp +++ b/native/graphics/jni/imagedecoder.cpp @@ -14,18 +14,9 @@ * limitations under the License. */ -#include "aassetstreamadaptor.h" - -#include <android/asset_manager.h> -#include <android/bitmap.h> -#include <android/data_space.h> -#include <android/imagedecoder.h> #include <MimeType.h> -#include <android/rect.h> -#include <hwui/ImageDecoder.h> -#include <log/log.h> -#include <SkAndroidCodec.h> #include <SkAlphaType.h> +#include <SkAndroidCodec.h> #include <SkCodec.h> #include <SkCodecAnimation.h> #include <SkColorSpace.h> @@ -35,14 +26,24 @@ #include <SkRefCnt.h> #include <SkSize.h> #include <SkStream.h> -#include <utils/Color.h> - +#include <android/asset_manager.h> +#include <android/bitmap.h> +#include <android/data_space.h> +#include <android/imagedecoder.h> +#include <android/rect.h> #include <fcntl.h> -#include <limits> -#include <optional> +#include <hwui/ImageDecoder.h> +#include <log/log.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> +#include <utils/Color.h> +#include <utils/StatsUtils.h> + +#include <limits> +#include <optional> + +#include "aassetstreamadaptor.h" using namespace android; @@ -400,9 +401,7 @@ size_t AImageDecoder_getMinimumStride(AImageDecoder* decoder) { return info.minRowBytes(); } -int AImageDecoder_decodeImage(AImageDecoder* decoder, - void* pixels, size_t stride, - size_t size) { +int AImageDecoder_decodeImage(AImageDecoder* decoder, void* pixels, size_t stride, size_t size) { if (!decoder || !pixels || !stride) { return ANDROID_IMAGE_DECODER_BAD_PARAMETER; } @@ -419,7 +418,13 @@ int AImageDecoder_decodeImage(AImageDecoder* decoder, return ANDROID_IMAGE_DECODER_FINISHED; } - return ResultToErrorCode(imageDecoder->decode(pixels, stride)); + const auto result = ResultToErrorCode(imageDecoder->decode(pixels, stride)); + + if (result == ANDROID_IMAGE_DECODER_SUCCESS) { + uirenderer::logBitmapDecode(imageDecoder->getOutputInfo(), false); + } + + return result; } void AImageDecoder_delete(AImageDecoder* decoder) { |