diff options
Diffstat (limited to 'native/android')
-rw-r--r-- | native/android/Android.bp | 4 | ||||
-rw-r--r-- | native/android/display_luts.cpp | 136 | ||||
-rw-r--r-- | native/android/dynamic_instrumentation_manager.cpp | 204 | ||||
-rw-r--r-- | native/android/include_platform/android/dynamic_instrumentation_manager.h | 133 | ||||
-rw-r--r-- | native/android/libandroid.map.txt | 59 | ||||
-rw-r--r-- | native/android/performance_hint.cpp | 756 | ||||
-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 | 278 | ||||
-rw-r--r-- | native/android/tests/performance_hint/PerformanceHintNativeTest.cpp | 162 | ||||
-rw-r--r-- | native/android/tests/thermal/NativeThermalUnitTest.cpp | 311 | ||||
-rw-r--r-- | native/android/thermal.cpp | 323 |
12 files changed, 2175 insertions, 258 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/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..074973188c66 --- /dev/null +++ b/native/android/dynamic_instrumentation_manager.cpp @@ -0,0 +1,204 @@ +/* + * 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) { + 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) { + 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) { + 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..ab9f37034a22 --- /dev/null +++ b/native/android/include_platform/android/dynamic_instrumentation_manager.h @@ -0,0 +1,133 @@ +/* + * 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 to disambiguate from corner cases that may arise from pid reuse. + */ +ADynamicInstrumentationManager_TargetProcess* _Nonnull + 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* _Nonnull instance) __INTRODUCED_IN(36); + +/** + * Initializes an ADynamicInstrumentationManager_MethodDescriptor. Caller must clean up when they + * are done with ADynamicInstrumentationManager_MethodDescriptor_Destroy. + * + * @param fullyQualifiedClassName fqcn of class containing the method. + * @param methodName + * @param fullyQualifiedParameters fqcn of parameters of the method's signature, or e.g. "int" for + * primitives. + * @param numParameters length of `fullyQualifiedParameters` array. + */ +ADynamicInstrumentationManager_MethodDescriptor* _Nonnull + ADynamicInstrumentationManager_MethodDescriptor_create( + const char* _Nonnull fullyQualifiedClassName, const char* _Nonnull methodName, + const char* _Nonnull fullyQualifiedParameters[_Nonnull], 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* _Nonnull 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. + */ +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 offset of the containing file within the process' memory. + */ +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 containing file. + */ +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* _Nonnull 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. + * @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..1ccadf90c2a9 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,23 @@ LIBANDROID { ASystemFontIterator_open; # introduced=29 ASystemFontIterator_close; # introduced=29 ASystemFontIterator_next; # introduced=29 + 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 +378,38 @@ 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 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: *; }; @@ -366,9 +419,15 @@ LIBANDROID_PLATFORM { AThermal_setIThermalServiceForTesting; APerformanceHint_setIHintManagerForTesting; APerformanceHint_sendHint; + APerformanceHint_setUseGraphicsPipelineForTesting; 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..9257901bcd1f 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> @@ -61,6 +73,34 @@ 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(); + } +}; + +bool kForceGraphicsPipeline = false; + +bool useGraphicsPipeline() { + return android::os::adpf_graphics_pipeline() || kForceGraphicsPipeline; +} + +// A pair of values that determine the behavior of the +// load hint rate limiter, to only allow "X hints every Y seconds" +constexpr double kLoadHintInterval = std::chrono::nanoseconds(2s).count(); +constexpr double kMaxLoadHintsPerInterval = 20; +constexpr double kReplenishRate = kMaxLoadHintsPerInterval / kLoadHintInterval; +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 +115,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 +126,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 +146,101 @@ private: size_t mAvailableSlots GUARDED_BY(sHintMutex) = 0; bool mHalSupported = true; HalMessageQueue::MemTransaction mFmqTransaction GUARDED_BY(sHintMutex); + std::future<bool> mChannelCreationFinished; +}; + +class SupportInfoWrapper { +public: + SupportInfoWrapper(hal::SupportInfo& info); + 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; + } + hal::SupportInfo mSupportInfo; +}; + +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); + + APerformanceHintSession* createSessionUsingConfig(ASessionCreationConfig* sessionCreationConfig, + 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(); 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, int numWindows, ASurfaceControl** controls, + int numSurfaceControls); private: friend struct APerformanceHintManager; @@ -171,17 +258,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 +292,27 @@ static FMQWrapper& getFMQ() { return APerformanceHintManager::getInstance()->getFMQWrapper(); } +// ===================================== SupportInfoWrapper implementation + +SupportInfoWrapper::SupportInfoWrapper(hal::SupportInfo& info) : mSupportInfo(info) {} + +bool SupportInfoWrapper::isSessionHintSupported(hal::SessionHint hint) { + return getEnumSupportFromBitfield(hint, mSupportInfo.sessionHints); +} + +bool SupportInfoWrapper::isSessionModeSupported(hal::SessionMode mode) { + return getEnumSupportFromBitfield(mode, mSupportInfo.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 +324,8 @@ APerformanceHintManager::~APerformanceHintManager() { } APerformanceHintManager* APerformanceHintManager::getInstance() { + static std::once_flag creationFlag; + static APerformanceHintManager* instance = nullptr; if (gHintManagerForTesting) { return gHintManagerForTesting.get(); } @@ -226,7 +334,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 +347,146 @@ 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); + + ASessionCreationConfig creationConfig{{ + .tids = std::vector<int32_t>(threadIds, threadIds + size), + .targetWorkDurationNanos = initialTargetWorkDurationNanos, + }}; + + return APerformanceHintManager::createSessionUsingConfig(&creationConfig, tag, isJava); +} + +APerformanceHintSession* APerformanceHintManager::createSessionUsingConfig( + ASessionCreationConfig* sessionCreationConfig, hal::SessionTag tag, bool isJava) { + std::shared_ptr<IHintSession> session; + 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; + } + } + } + + ret = mHintManager->createHintSessionWithConfig(mToken, tag, + *static_cast<SessionCreationConfig*>( + sessionCreationConfig), + &sessionConfig, &session); + + sessionCreationConfig->layerTokens.clear(); if (!ret.isOk() || !session) { ALOGE("%s: PerformanceHint cannot create session. %s", __FUNCTION__, ret.getMessage()); return nullptr; } - auto out = new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos, - initialTargetWorkDurationNanos, + + auto out = new APerformanceHintSession(mHintManager, std::move(session), + mClientData.preferredRateNanos, + sessionCreationConfig->targetWorkDurationNanos, isJava, sessionConfig.id == -1 ? std::nullopt : std::make_optional<hal::SessionConfig>( std::move(sessionConfig))); 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); + + return out; +} + +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; +} -constexpr int kNumEnums = - ndk::enum_range<hal::SessionHint>().end() - ndk::enum_range<hal::SessionHint>().begin(); +ndk::SpAIBinder& APerformanceHintManager::getToken() { + return mToken; +} + +SupportInfoWrapper& APerformanceHintManager::getSupportInfo() { + return mSupportInfoWrapper; +} +// ===================================== 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 +495,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"); @@ -357,31 +550,100 @@ int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNano return reportActualWorkDurationInternal(static_cast<AWorkDuration*>(&workDuration)); } -int APerformanceHintSession::sendHint(SessionHint hint) { +bool APerformanceHintSession::isJava() { + return mIsJava; +} + +int APerformanceHintSession::sendHints(std::vector<hal::SessionHint>& hints, int64_t now, + const char*) { std::scoped_lock lock(sHintMutex); - if (hint < 0 || hint >= static_cast<int32_t>(mLastHintSentTimestamp.size())) { - ALOGE("%s: invalid session hint %d", __FUNCTION__, hint); + if (hints.empty()) { return EINVAL; } - int64_t now = uptimeNanos(); + for (auto&& hint : hints) { + if (static_cast<int32_t>(hint) < 0 || static_cast<int32_t>(hint) >= kNumEnums) { + ALOGE("%s: invalid session hint %d", __FUNCTION__, hint); + return EINVAL; + } + } - // Limit sendHint to a pre-detemined rate for safety - if (now < (mLastHintSentTimestamp[hint] + SEND_HINT_TIMEOUT)) { - return 0; + 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)] + SEND_HINT_TIMEOUT)) { + return EBUSY; + } + } } - if (!getFMQ().sendHint(mSessionConfig, hint)) { - ndk::ScopedAStatus ret = mHintSession->sendHint(hint); + 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 (!ret.isOk()) { + ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.getMessage()); + return EPIPE; + } } } - mLastHintSentTimestamp[hint] = now; + + if (!useNewLoadHintBehavior()) { + for (auto&& hint : hints) { + mLastHintSentTimestamp[static_cast<int32_t>(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__); @@ -495,6 +757,57 @@ int APerformanceHintSession::reportActualWorkDurationInternal(AWorkDuration* wor return 0; } +status_t APerformanceHintSession::setNativeSurfaces(ANativeWindow** windows, int numWindows, + ASurfaceControl** controls, + int 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))); + } + + mHintSession->associateToLayers(ndkLayerHandles); + 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::isValid(surface)) { + 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,25 +835,28 @@ 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; + }); } return isActive(); } @@ -558,24 +874,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 +905,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 +920,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 +952,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 +966,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 +992,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); } @@ -717,6 +1058,22 @@ APerformanceHintSession* APerformanceHint_createSession(APerformanceHintManager* return manager->createSession(threadIds, size, initialTargetWorkDurationNanos); } +APerformanceHintSession* APerformanceHint_createSessionUsingConfig( + APerformanceHintManager* manager, ASessionCreationConfig* sessionCreationConfig) { + VALIDATE_PTR(manager); + VALIDATE_PTR(sessionCreationConfig); + return manager->createSessionUsingConfig(sessionCreationConfig); +} + +APerformanceHintSession* APerformanceHint_createSessionUsingConfigInternal( + APerformanceHintManager* manager, ASessionCreationConfig* sessionCreationConfig, + SessionTag tag) { + VALIDATE_PTR(manager); + VALIDATE_PTR(sessionCreationConfig); + return manager->createSessionUsingConfig(sessionCreationConfig, + static_cast<hal::SessionTag>(tag)); +} + APerformanceHintSession* APerformanceHint_createSessionInternal( APerformanceHintManager* manager, const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos, SessionTag tag) { @@ -726,11 +1083,31 @@ 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) @@ -746,12 +1123,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 +1173,44 @@ 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) + if (!useNewLoadHintBehavior()) { + return ENOTSUP; + } + return session->notifyWorkloadIncrease(cpu, gpu, debugName); +} + +int APerformanceHint_notifyWorkloadReset(APerformanceHintSession* session, bool cpu, bool gpu, + const char* debugName) { + VALIDATE_PTR(session) + VALIDATE_PTR(debugName) + if (!useNewLoadHintBehavior()) { + return ENOTSUP; + } + return session->notifyWorkloadReset(cpu, gpu, debugName); +} + +int APerformanceHint_notifyWorkloadSpike(APerformanceHintSession* session, bool cpu, bool gpu, + const char* debugName) { + VALIDATE_PTR(session) + VALIDATE_PTR(debugName) + if (!useNewLoadHintBehavior()) { + return ENOTSUP; + } + return session->notifyWorkloadSpike(cpu, gpu, debugName); +} + +int APerformanceHint_setNativeSurfaces(APerformanceHintSession* session, + ANativeWindow** nativeWindows, int nativeWindowsSize, + ASurfaceControl** surfaceControls, int surfaceControlsSize) { + VALIDATE_PTR(session) + return session->setNativeSurfaces(nativeWindows, nativeWindowsSize, surfaceControls, + surfaceControlsSize); +} + AWorkDuration* AWorkDuration_create() { return new AWorkDuration(); } @@ -831,3 +1258,138 @@ 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; +} + +int ASessionCreationConfig_setTids(ASessionCreationConfig* config, const pid_t* tids, size_t size) { + VALIDATE_PTR(config) + VALIDATE_PTR(tids) + + if (!useGraphicsPipeline()) { + return ENOTSUP; + } + + if (size <= 0) { + LOG_ALWAYS_FATAL_IF(size <= 0, + "%s: Invalid value. Thread id list size should be greater than zero.", + __FUNCTION__); + return EINVAL; + } + config->tids = std::vector<int32_t>(tids, tids + size); + return 0; +} + +int ASessionCreationConfig_setTargetWorkDurationNanos(ASessionCreationConfig* config, + int64_t targetWorkDurationNanos) { + VALIDATE_PTR(config) + VALIDATE_INT(targetWorkDurationNanos, >= 0) + + if (!useGraphicsPipeline()) { + return ENOTSUP; + } + + config->targetWorkDurationNanos = targetWorkDurationNanos; + return 0; +} + +int ASessionCreationConfig_setPreferPowerEfficiency(ASessionCreationConfig* config, bool enabled) { + VALIDATE_PTR(config) + + if (!useGraphicsPipeline()) { + return ENOTSUP; + } + + if (enabled) { + config->modesToEnable.push_back(hal::SessionMode::POWER_EFFICIENCY); + } else { + std::erase(config->modesToEnable, hal::SessionMode::POWER_EFFICIENCY); + } + return 0; +} + +int ASessionCreationConfig_setGraphicsPipeline(ASessionCreationConfig* config, bool enabled) { + VALIDATE_PTR(config) + + if (!useGraphicsPipeline()) { + return ENOTSUP; + } + + if (enabled) { + config->modesToEnable.push_back(hal::SessionMode::GRAPHICS_PIPELINE); + } else { + std::erase(config->modesToEnable, hal::SessionMode::GRAPHICS_PIPELINE); + + // Remove automatic timing modes if we turn off GRAPHICS_PIPELINE, + // as it is a strict pre-requisite for these to run + std::erase(config->modesToEnable, hal::SessionMode::AUTO_CPU); + std::erase(config->modesToEnable, hal::SessionMode::AUTO_GPU); + } + return 0; +} + +void APerformanceHint_setUseGraphicsPipelineForTesting(bool enabled) { + kForceGraphicsPipeline = enabled; +} + +void APerformanceHint_getRateLimiterPropertiesForTesting(int32_t* maxLoadHintsPerInterval, + int64_t* loadHintInterval) { + *maxLoadHintsPerInterval = kMaxLoadHintsPerInterval; + *loadHintInterval = kLoadHintInterval; +} + +void APerformanceHint_setUseNewLoadHintBehaviorForTesting(bool newBehavior) { + kForceNewHintBehavior = newBehavior; +} + +int ASessionCreationConfig_setNativeSurfaces(ASessionCreationConfig* config, + ANativeWindow** nativeWindows, int nativeWindowsSize, + ASurfaceControl** surfaceControls, + int surfaceControlsSize) { + VALIDATE_PTR(config) + + APerformanceHintManager::layersFromNativeSurfaces<wp<IBinder>>(nativeWindows, nativeWindowsSize, + surfaceControls, + surfaceControlsSize, + config->layers); + + if (config->layers.empty()) { + return EINVAL; + } + + return 0; +} + +int ASessionCreationConfig_setUseAutoTiming(ASessionCreationConfig* _Nonnull config, bool cpu, + bool gpu) { + VALIDATE_PTR(config) + if ((cpu || gpu) && !config->hasMode(hal::SessionMode::GRAPHICS_PIPELINE)) { + ALOGE("Automatic timing is not supported unless graphics pipeline mode is enabled first"); + return ENOTSUP; + } + + if (config->hasMode(hal::SessionMode::AUTO_CPU)) { + if (!cpu) { + std::erase(config->modesToEnable, hal::SessionMode::AUTO_CPU); + } + } else if (cpu) { + config->modesToEnable.push_back(static_cast<hal::SessionMode>(hal::SessionMode::AUTO_CPU)); + } + + if (config->hasMode(hal::SessionMode::AUTO_GPU)) { + if (!gpu) { + std::erase(config->modesToEnable, hal::SessionMode::AUTO_GPU); + } + } else if (gpu) { + config->modesToEnable.push_back(static_cast<hal::SessionMode>(hal::SessionMode::AUTO_GPU)); + } + + return 0; +} 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..f3fa9f6836d5 --- /dev/null +++ b/native/android/system_health.cpp @@ -0,0 +1,278 @@ +/* + * 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. + */ + +#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> + +using namespace android; +using namespace aidl::android::os; +namespace hal = aidl::android::hardware::power; + +struct ACpuHeadroomParams : public CpuHeadroomParamsInternal {}; +struct AGpuHeadroomParams : public GpuHeadroomParamsInternal {}; + +const int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; +const int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; +const int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; +const int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; +const int CPU_HEADROOM_MAX_TID_COUNT = 5; + +struct ASystemHealthManager { +public: + static ASystemHealthManager* getInstance(); + ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager); + 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); + +private: + static ASystemHealthManager* create(std::shared_ptr<IHintManager> hintManager); + std::shared_ptr<IHintManager> mHintManager; +}; + +ASystemHealthManager* ASystemHealthManager::getInstance() { + static std::once_flag creationFlag; + static ASystemHealthManager* instance = nullptr; + std::call_once(creationFlag, []() { instance = create(nullptr); }); + return instance; +} + +ASystemHealthManager::ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager) + : mHintManager(std::move(hintManager)) {} + +ASystemHealthManager::~ASystemHealthManager() {} + +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; + } + return new ASystemHealthManager(hintManager); +} + +ASystemHealthManager* ASystemHealth_acquireManager() { + return ASystemHealthManager::getInstance(); +} + +int ASystemHealthManager::getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom) { + std::optional<hal::CpuHeadroomResult> res; + ::ndk::ScopedAStatus ret; + CpuHeadroomParamsInternal internalParams; + if (!params) { + ret = mHintManager->getCpuHeadroom(internalParams, &res); + } else { + 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->get<hal::CpuHeadroomResult::Tag::globalHeadroom>(); + return OK; +} + +int ASystemHealthManager::getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom) { + std::optional<hal::GpuHeadroomResult> res; + ::ndk::ScopedAStatus ret; + GpuHeadroomParamsInternal internalParams; + if (!params) { + ret = mHintManager->getGpuHeadroom(internalParams, &res); + } else { + 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->get<hal::GpuHeadroomResult::Tag::globalHeadroom>(); + return OK; +} + +int ASystemHealthManager::getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) { + int64_t minIntervalMillis = 0; + ::ndk::ScopedAStatus ret = mHintManager->getCpuHeadroomMinIntervalMillis(&minIntervalMillis); + if (!ret.isOk()) { + ALOGE("ASystemHealth_getCpuHeadroomMinIntervalMillis fails: %s", ret.getMessage()); + if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { + return ENOTSUP; + } + return EPIPE; + } + *outMinIntervalMillis = minIntervalMillis; + return OK; +} + +int ASystemHealthManager::getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) { + int64_t minIntervalMillis = 0; + ::ndk::ScopedAStatus ret = mHintManager->getGpuHeadroomMinIntervalMillis(&minIntervalMillis); + if (!ret.isOk()) { + ALOGE("ASystemHealth_getGpuHeadroomMinIntervalMillis fails: %s", ret.getMessage()); + if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { + return ENOTSUP; + } + return EPIPE; + } + *outMinIntervalMillis = minIntervalMillis; + return OK; +} + +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 < CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN || + windowMillis > CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX, + "%s: windowMillis should be in range [50, 10000] but got %d", __FUNCTION__, + windowMillis); + params->calculationWindowMillis = windowMillis; +} + +void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params, + int windowMillis) { + LOG_ALWAYS_FATAL_IF(windowMillis < GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN || + windowMillis > GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX, + "%s: windowMillis should be in range [50, 10000] 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, + int tidsSize) { + LOG_ALWAYS_FATAL_IF(tids == nullptr, "%s: tids should not be null", __FUNCTION__); + LOG_ALWAYS_FATAL_IF(tidsSize > CPU_HEADROOM_MAX_TID_COUNT, "%s: tids size should not exceed 5", + __FUNCTION__); + params->tids.resize(tidsSize); + params->tids.clear(); + for (int i = 0; i < 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* _Nonnull params) { + delete params; +} + +void AGpuHeadroomParams_destroy(AGpuHeadroomParams* _Nonnull params) { + delete params; +} diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index 9de3a6f525e6..e3c10f63abb4 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; @@ -49,11 +52,10 @@ using namespace testing; 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, + (const SpAIBinder& token, hal::SessionTag tag, + const SessionCreationConfig& creationConfig, hal::SessionConfig* config, std::shared_ptr<IHintSession>* _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 +68,24 @@ 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(SpAIBinder, asBinder, (), (override)); MOCK_METHOD(bool, isRemote, (), (override)); }; @@ -82,6 +102,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 +112,11 @@ class PerformanceHintTest : public Test { public: void SetUp() override { mMockIHintManager = ndk::SharedRefBase::make<NiceMock<MockIHintManager>>(); + APerformanceHint_getRateLimiterPropertiesForTesting(&mMaxLoadHintsPerInterval, + &mLoadHintInterval); APerformanceHint_setIHintManagerForTesting(&mMockIHintManager); + APerformanceHint_setUseGraphicsPipelineForTesting(true); + APerformanceHint_setUseNewLoadHintBehaviorForTesting(true); } void TearDown() override { @@ -101,23 +127,23 @@ 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(); })); return APerformanceHint_getManager(); } APerformanceHintSession* createSession(APerformanceHintManager* manager, int64_t targetDuration = 56789L, bool isHwui = false) { mMockSession = ndk::SharedRefBase::make<NiceMock<MockIHintSession>>(); - int64_t sessionId = 123; + const 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)), + ON_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _)) + .WillByDefault(DoAll(SetArgPointee<3>(hal::SessionConfig({.id = sessionId})), + SetArgPointee<4>(std::shared_ptr<IHintSession>(mMockSession)), [] { return ScopedAStatus::ok(); })); ON_CALL(*mMockIHintManager, setHintSessionThreads(_, _)).WillByDefault([] { @@ -142,6 +168,43 @@ public: return APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration); } + APerformanceHintSession* createSessionUsingConfig(APerformanceHintManager* manager, + SessionCreationConfig config, + bool isHwui = false) { + mMockSession = ndk::SharedRefBase::make<NiceMock<MockIHintSession>>(); + const int64_t sessionId = 123; + + ON_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _)) + .WillByDefault(DoAll(SetArgPointee<3>(hal::SessionConfig({.id = sessionId})), + SetArgPointee<4>(std::shared_ptr<IHintSession>(mMockSession)), + [] { return ScopedAStatus::ok(); })); + + ON_CALL(*mMockIHintManager, setHintSessionThreads(_, _)).WillByDefault([] { + return ScopedAStatus::ok(); + }); + ON_CALL(*mMockSession, sendHint(_)).WillByDefault([] { return ScopedAStatus::ok(); }); + ON_CALL(*mMockSession, setMode(_, true)).WillByDefault([] { return ScopedAStatus::ok(); }); + ON_CALL(*mMockSession, close()).WillByDefault([] { return ScopedAStatus::ok(); }); + ON_CALL(*mMockSession, updateTargetWorkDuration(_)).WillByDefault([] { + return ScopedAStatus::ok(); + }); + ON_CALL(*mMockSession, reportActualWorkDuration(_, _)).WillByDefault([] { + return ScopedAStatus::ok(); + }); + ON_CALL(*mMockSession, reportActualWorkDuration2(_)).WillByDefault([] { + return ScopedAStatus::ok(); + }); + + if (isHwui) { + return APerformanceHint_createSessionUsingConfigInternal( + manager, reinterpret_cast<ASessionCreationConfig*>(&config), SessionTag::HWUI); + } + + return APerformanceHint_createSessionUsingConfig(manager, + reinterpret_cast<ASessionCreationConfig*>( + &config)); + } + void setFMQEnabled(bool enabled) { mUsingFMQ = enabled; if (enabled) { @@ -176,6 +239,23 @@ public: int kMockQueueSize = 20; bool mUsingFMQ = false; + 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) { hal::ChannelMessage readData; @@ -191,12 +271,6 @@ 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); @@ -218,7 +292,6 @@ TEST_F(PerformanceHintTest, TestSession) { EXPECT_CALL(*mMockSession, reportActualWorkDuration2(_)).Times(Exactly(1)); result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos); EXPECT_EQ(0, result); - result = APerformanceHint_updateTargetWorkDuration(session, -1L); EXPECT_EQ(EINVAL, result); result = APerformanceHint_reportActualWorkDuration(session, -1L); @@ -228,33 +301,57 @@ TEST_F(PerformanceHintTest, TestSession) { EXPECT_CALL(*mMockSession, sendHint(Eq(hintId))).Times(Exactly(1)); result = APerformanceHint_sendHint(session, 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, 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, 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, true, true, "Test hint"); EXPECT_EQ(0, result); result = APerformanceHint_sendHint(session, static_cast<SessionHint>(-1)); EXPECT_EQ(EINVAL, result); + Mock::VerifyAndClearExpectations(mMockSession.get()); + for (int i = 0; i < mMaxLoadHintsPerInterval; ++i) { + APerformanceHint_sendHint(session, 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, true, true, "Test hint"); + EXPECT_EQ(result, EBUSY); + EXPECT_CALL(*mMockSession, sendHint(_)).Times(Exactly(0)); + result = APerformanceHint_notifyWorkloadReset(session, 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); ASSERT_TRUE(session); APerformanceHint_closeSession(session); } +TEST_F(PerformanceHintTest, TestSessionCreationUsingConfig) { + EXPECT_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _)).Times(1); + SessionCreationConfig config{.tids = std::vector<int32_t>(1, 2), + .targetWorkDurationNanos = 5678, + .modesToEnable = std::vector<hal::SessionMode>(0)}; + APerformanceHintManager* manager = createManager(); + APerformanceHintSession* 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); @@ -420,3 +517,16 @@ TEST_F(PerformanceHintTest, TestReportActualUsingFMQ) { reinterpret_cast<AWorkDuration*>(&duration)); expectToReadFromFmq<HalChannelMessageContents::Tag::workDuration>(durationExpected); } + +TEST_F(PerformanceHintTest, TestASessionCreationConfig) { + ASessionCreationConfig* config = ASessionCreationConfig_create(); + ASSERT_NE(config, nullptr); + + const int32_t testTids[2] = {1, 2}; + const size_t size = 2; + EXPECT_EQ(ASessionCreationConfig_setTids(config, testTids, size), 0); + EXPECT_EQ(ASessionCreationConfig_setTargetWorkDurationNanos(config, 20), 0); + EXPECT_EQ(ASessionCreationConfig_setPreferPowerEfficiency(config, true), 0); + EXPECT_EQ(ASessionCreationConfig_setGraphicsPipeline(config, true), 0); + ASessionCreationConfig_release(config); +} 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); +} |