diff --git a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibrator.aidl b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibrator.aidl index af619c6a98..0dcc657955 100644 --- a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibrator.aidl +++ b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/IVibrator.aidl @@ -59,6 +59,11 @@ interface IVibrator { android.hardware.vibrator.Braking[] getSupportedBraking(); void composePwle(in android.hardware.vibrator.PrimitivePwle[] composite, in android.hardware.vibrator.IVibratorCallback callback); void performVendorEffect(in android.hardware.vibrator.VendorEffect vendorEffect, in android.hardware.vibrator.IVibratorCallback callback); + List getPwleV2FrequencyToOutputAccelerationMap(); + int getPwleV2PrimitiveDurationMaxMillis(); + int getPwleV2CompositionSizeMax(); + int getPwleV2PrimitiveDurationMinMillis(); + void composePwleV2(in android.hardware.vibrator.PwleV2Primitive[] composite, in android.hardware.vibrator.IVibratorCallback callback); const int CAP_ON_CALLBACK = (1 << 0) /* 1 */; const int CAP_PERFORM_CALLBACK = (1 << 1) /* 2 */; const int CAP_AMPLITUDE_CONTROL = (1 << 2) /* 4 */; @@ -71,4 +76,5 @@ interface IVibrator { const int CAP_FREQUENCY_CONTROL = (1 << 9) /* 512 */; const int CAP_COMPOSE_PWLE_EFFECTS = (1 << 10) /* 1024 */; const int CAP_PERFORM_VENDOR_EFFECTS = (1 << 11) /* 2048 */; + const int CAP_COMPOSE_PWLE_EFFECTS_V2 = (1 << 12) /* 4096 */; } diff --git a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/PwleV2OutputMapEntry.aidl b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/PwleV2OutputMapEntry.aidl new file mode 100644 index 0000000000..a5eda52bc7 --- /dev/null +++ b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/PwleV2OutputMapEntry.aidl @@ -0,0 +1,39 @@ +/* + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.vibrator; +@VintfStability +parcelable PwleV2OutputMapEntry { + float frequencyHz; + float maxOutputAccelerationGs; +} diff --git a/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/PwleV2Primitive.aidl b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/PwleV2Primitive.aidl new file mode 100644 index 0000000000..c4f3ea955a --- /dev/null +++ b/vibrator/aidl/aidl_api/android.hardware.vibrator/current/android/hardware/vibrator/PwleV2Primitive.aidl @@ -0,0 +1,40 @@ +/* + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.vibrator; +@VintfStability +parcelable PwleV2Primitive { + float amplitude; + float frequencyHz; + int timeMillis; +} diff --git a/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl b/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl index 768ec4f658..11f36baf2c 100644 --- a/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl +++ b/vibrator/aidl/android/hardware/vibrator/IVibrator.aidl @@ -23,6 +23,8 @@ import android.hardware.vibrator.Effect; import android.hardware.vibrator.EffectStrength; import android.hardware.vibrator.IVibratorCallback; import android.hardware.vibrator.PrimitivePwle; +import android.hardware.vibrator.PwleV2OutputMapEntry; +import android.hardware.vibrator.PwleV2Primitive; import android.hardware.vibrator.VendorEffect; @VintfStability @@ -75,6 +77,10 @@ interface IVibrator { * Whether perform w/ vendor effect is supported. */ const int CAP_PERFORM_VENDOR_EFFECTS = 1 << 11; + /** + * Whether composePwleV2 for PwlePrimitives is supported. + */ + const int CAP_COMPOSE_PWLE_EFFECTS_V2 = 1 << 12; /** * Determine capabilities of the vibrator HAL (CAP_* mask) @@ -385,4 +391,82 @@ interface IVibrator { * - EX_SERVICE_SPECIFIC for bad vendor data, vibration is not triggered. */ void performVendorEffect(in VendorEffect vendorEffect, in IVibratorCallback callback); + + /** + * Retrieves a mapping of vibration frequency (Hz) to the maximum achievable output + * acceleration (Gs) the device can reach at that frequency. + * + * The map, represented as a list of `PwleV2OutputMapEntry` (frequency, output acceleration) + * pairs, defines the device's frequency response. The platform uses the minimum and maximum + * frequency values to determine the supported input range for `IVibrator.composePwleV2`. + * Output acceleration values are used to identify a frequency range suitable to safely play + * perceivable vibrations with a simple API. The map is also exposed for developers using an + * advanced API. + * + * The platform does not impose specific requirements on map resolution which can vary + * depending on the shape of device output curve. The values will be linearly interpolated + * during lookups. The platform will provide a simple API, defined by the first frequency range + * where output acceleration consistently exceeds a minimum threshold of 10 db SL. + * + * + * This may not be supported and this support is reflected in getCapabilities + * (CAP_COMPOSE_PWLE_EFFECTS_V2). If this is supported, it's expected to be non-empty and + * describe a valid non-empty frequency range where the simple API can be defined + * (i.e. a range where the output acceleration is always above 10 db SL). + * + * @return A list of map entries representing the frequency to max acceleration + * mapping. + * @throws EX_UNSUPPORTED_OPERATION if unsupported, as reflected by getCapabilities. + */ + List getPwleV2FrequencyToOutputAccelerationMap(); + + /** + * Retrieve the maximum duration allowed for any primitive PWLE in units of + * milliseconds. + * + * This may not be supported and this support is reflected in + * getCapabilities (CAP_COMPOSE_PWLE_EFFECTS_V2). + * + * @return The maximum duration allowed for a single PrimitivePwle. Non-zero value if supported. + * @throws EX_UNSUPPORTED_OPERATION if unsupported, as reflected by getCapabilities. + */ + int getPwleV2PrimitiveDurationMaxMillis(); + + /** + * Retrieve the maximum number of PWLE primitives input supported by IVibrator.composePwleV2. + * + * This may not be supported and this support is reflected in + * getCapabilities (CAP_COMPOSE_PWLE_EFFECTS_V2). Devices supporting PWLE effects must + * support effects with at least 16 PwleV2Primitive. + * + * @return The maximum count allowed. Non-zero value if supported. + * @throws EX_UNSUPPORTED_OPERATION if unsupported, as reflected by getCapabilities. + */ + int getPwleV2CompositionSizeMax(); + + /** + * Retrieves the minimum duration (in milliseconds) of any segment within a + * PWLE effect. Devices supporting PWLE effects must support a minimum ramp + * time of 20 milliseconds. + * + * This may not be supported and this support is reflected in + * getCapabilities (CAP_COMPOSE_PWLE_EFFECTS_V2). + * + * @return The minimum duration allowed for a single PrimitivePwle. Non-zero value if supported. + * @throws EX_UNSUPPORTED_OPERATION if unsupported, as reflected by getCapabilities. + */ + int getPwleV2PrimitiveDurationMinMillis(); + + /** + * Play composed sequence of chirps with optional callback upon completion. + * + * This may not be supported and this support is reflected in + * getCapabilities (CAP_COMPOSE_PWLE_EFFECTS_V2). + * + * Doing this operation while the vibrator is already on is undefined behavior. Clients should + * explicitly call off. IVibratorCallback.onComplete() support is required for this API. + * + * @param composite An array of primitives that represents a PWLE (Piecewise-Linear Envelope). + */ + void composePwleV2(in PwleV2Primitive[] composite, in IVibratorCallback callback); } diff --git a/vibrator/aidl/android/hardware/vibrator/PwleV2OutputMapEntry.aidl b/vibrator/aidl/android/hardware/vibrator/PwleV2OutputMapEntry.aidl new file mode 100644 index 0000000000..a8db87cd25 --- /dev/null +++ b/vibrator/aidl/android/hardware/vibrator/PwleV2OutputMapEntry.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.vibrator; + +@VintfStability +parcelable PwleV2OutputMapEntry { + /** + * Absolute frequency point in the units of hertz + * + */ + float frequencyHz; + + /** + * Max output acceleration for the specified frequency in units of Gs. + * + * This value represents the maximum safe output acceleration (in Gs) achievable at the + * specified frequency, typically determined during calibration. The actual output acceleration + * is assumed to scale linearly with the input amplitude within the range of [0, 1]. + */ + float maxOutputAccelerationGs; +} diff --git a/vibrator/aidl/android/hardware/vibrator/PwleV2Primitive.aidl b/vibrator/aidl/android/hardware/vibrator/PwleV2Primitive.aidl new file mode 100644 index 0000000000..bd7bec6036 --- /dev/null +++ b/vibrator/aidl/android/hardware/vibrator/PwleV2Primitive.aidl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.vibrator; + +@VintfStability +parcelable PwleV2Primitive { + /** + * Input amplitude ranges from 0.0 (inclusive) to 1.0 (inclusive), representing the relative + * input value. Actual output acceleration depends on frequency and device response curve + * (see IVibrator.getPwleV2FrequencyToOutputAccelerationMap for max values). + * + * Input amplitude linearly maps to output acceleration (e.g., 0.5 amplitude yields half the + * max acceleration for that frequency). + * + * 0.0 represents no output acceleration amplitude + * 1.0 represents the maximum achievable strength for each frequency, as determined by the + * actuator response curve + */ + float amplitude; + + /** + * Absolute frequency point in the units of hertz + * + * Values are within the continuous inclusive frequency range defined by + * IVibrator#getPwleV2FrequencyToOutputAccelerationMap. + */ + float frequencyHz; + + /* Total time from the previous PWLE point to the current one in units of milliseconds. */ + int timeMillis; +} diff --git a/vibrator/aidl/default/Vibrator.cpp b/vibrator/aidl/default/Vibrator.cpp index 29e7d1830e..4f020a017a 100644 --- a/vibrator/aidl/default/Vibrator.cpp +++ b/vibrator/aidl/default/Vibrator.cpp @@ -27,9 +27,12 @@ namespace vibrator { static constexpr int32_t COMPOSE_DELAY_MAX_MS = 1000; static constexpr int32_t COMPOSE_SIZE_MAX = 256; static constexpr int32_t COMPOSE_PWLE_SIZE_MAX = 127; +static constexpr int32_t COMPOSE_PWLE_V2_SIZE_MAX = 16; static constexpr float Q_FACTOR = 11.0; static constexpr int32_t COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS = 16383; +static constexpr int32_t COMPOSE_PWLE_V2_PRIMITIVE_DURATION_MAX_MS = 1000; +static constexpr int32_t COMPOSE_PWLE_V2_PRIMITIVE_DURATION_MIN_MS = 20; static constexpr float PWLE_LEVEL_MIN = 0.0; static constexpr float PWLE_LEVEL_MAX = 1.0; static constexpr float PWLE_FREQUENCY_RESOLUTION_HZ = 1.0; @@ -44,12 +47,25 @@ static constexpr int32_t ERROR_CODE_INVALID_DURATION = 1; ndk::ScopedAStatus Vibrator::getCapabilities(int32_t* _aidl_return) { LOG(VERBOSE) << "Vibrator reporting capabilities"; - *_aidl_return = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK | - IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_EXTERNAL_CONTROL | - IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL | IVibrator::CAP_COMPOSE_EFFECTS | - IVibrator::CAP_ALWAYS_ON_CONTROL | IVibrator::CAP_GET_RESONANT_FREQUENCY | - IVibrator::CAP_GET_Q_FACTOR | IVibrator::CAP_FREQUENCY_CONTROL | - IVibrator::CAP_COMPOSE_PWLE_EFFECTS | IVibrator::CAP_PERFORM_VENDOR_EFFECTS; + std::lock_guard lock(mMutex); + if (mCapabilities == 0) { + if (!getInterfaceVersion(&mVersion).isOk()) { + return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_STATE)); + } + mCapabilities = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK | + IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_EXTERNAL_CONTROL | + IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL | IVibrator::CAP_COMPOSE_EFFECTS | + IVibrator::CAP_ALWAYS_ON_CONTROL | IVibrator::CAP_GET_RESONANT_FREQUENCY | + IVibrator::CAP_GET_Q_FACTOR | IVibrator::CAP_FREQUENCY_CONTROL | + IVibrator::CAP_COMPOSE_PWLE_EFFECTS; + + if (mVersion >= 3) { + mCapabilities |= (IVibrator::CAP_PERFORM_VENDOR_EFFECTS | + IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2); + } + } + + *_aidl_return = mCapabilities; return ndk::ScopedAStatus::ok(); } @@ -108,6 +124,13 @@ ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength strength, ndk::ScopedAStatus Vibrator::performVendorEffect( const VendorEffect& effect, const std::shared_ptr& callback) { LOG(VERBOSE) << "Vibrator perform vendor effect"; + int32_t capabilities = 0; + if (!getCapabilities(&capabilities).isOk()) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + if ((capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) == 0) { + return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); + } EffectStrength strength = effect.strength; if (strength != EffectStrength::LIGHT && strength != EffectStrength::MEDIUM && strength != EffectStrength::STRONG) { @@ -449,6 +472,114 @@ ndk::ScopedAStatus Vibrator::composePwle(const std::vector &compo return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus Vibrator::getPwleV2FrequencyToOutputAccelerationMap( + std::vector* _aidl_return) { + std::vector frequencyToOutputAccelerationMap; + + std::vector> frequencyToOutputAccelerationData = { + {30.0f, 0.01f}, {46.0f, 0.09f}, {50.0f, 0.1f}, {55.0f, 0.12f}, {62.0f, 0.66f}, + {83.0f, 0.82f}, {85.0f, 0.85f}, {92.0f, 1.05f}, {107.0f, 1.63f}, {115.0f, 1.72f}, + {123.0f, 1.81f}, {135.0f, 2.23f}, {144.0f, 2.47f}, {145.0f, 2.5f}, {150.0f, 3.0f}, + {175.0f, 2.51f}, {181.0f, 2.41f}, {190.0f, 2.28f}, {200.0f, 2.08f}, {204.0f, 1.96f}, + {205.0f, 1.9f}, {224.0f, 1.7f}, {235.0f, 1.5f}, {242.0f, 1.46f}, {253.0f, 1.41f}, + {263.0f, 1.39f}, {65.0f, 1.38f}, {278.0f, 1.37f}, {294.0f, 1.35f}, {300.0f, 1.34f}}; + for (const auto& entry : frequencyToOutputAccelerationData) { + frequencyToOutputAccelerationMap.push_back( + PwleV2OutputMapEntry(/*frequency=*/entry.first, + /*maxOutputAcceleration=*/entry.second)); + } + + *_aidl_return = frequencyToOutputAccelerationMap; + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getPwleV2PrimitiveDurationMaxMillis(int32_t* maxDurationMs) { + *maxDurationMs = COMPOSE_PWLE_V2_PRIMITIVE_DURATION_MAX_MS; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getPwleV2CompositionSizeMax(int32_t* maxSize) { + *maxSize = COMPOSE_PWLE_V2_SIZE_MAX; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getPwleV2PrimitiveDurationMinMillis(int32_t* minDurationMs) { + *minDurationMs = COMPOSE_PWLE_V2_PRIMITIVE_DURATION_MIN_MS; + return ndk::ScopedAStatus::ok(); +} + +float getPwleV2FrequencyMinHz(std::vector frequencyToOutputAccelerationMap) { + if (frequencyToOutputAccelerationMap.empty()) { + return 0.0f; + } + + float minFrequency = frequencyToOutputAccelerationMap[0].frequencyHz; + + for (const auto& entry : frequencyToOutputAccelerationMap) { + if (entry.frequencyHz < minFrequency) { + minFrequency = entry.frequencyHz; + } + } + + return minFrequency; +} + +float getPwleV2FrequencyMaxHz(std::vector frequencyToOutputAccelerationMap) { + if (frequencyToOutputAccelerationMap.empty()) { + return 0.0f; + } + + float maxFrequency = frequencyToOutputAccelerationMap[0].frequencyHz; + + for (const auto& entry : frequencyToOutputAccelerationMap) { + if (entry.frequencyHz > maxFrequency) { + maxFrequency = entry.frequencyHz; + } + } + + return maxFrequency; +} + +ndk::ScopedAStatus Vibrator::composePwleV2(const std::vector& composite, + const std::shared_ptr& callback) { + int compositionSizeMax; + getPwleV2CompositionSizeMax(&compositionSizeMax); + if (composite.size() <= 0 || composite.size() > compositionSizeMax) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + int32_t totalEffectDuration = 0; + std::vector frequencyToOutputAccelerationMap; + getPwleV2FrequencyToOutputAccelerationMap(&frequencyToOutputAccelerationMap); + float minFrequency = getPwleV2FrequencyMinHz(frequencyToOutputAccelerationMap); + float maxFrequency = getPwleV2FrequencyMaxHz(frequencyToOutputAccelerationMap); + + for (auto& e : composite) { + if (e.timeMillis < 0.0f || e.timeMillis > COMPOSE_PWLE_V2_PRIMITIVE_DURATION_MAX_MS) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (e.amplitude < 0.0f || e.amplitude > 1.0f) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (e.frequencyHz < minFrequency || e.frequencyHz > maxFrequency) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + totalEffectDuration += e.timeMillis; + } + + std::thread([totalEffectDuration, callback] { + LOG(VERBOSE) << "Starting composePwleV2 on another thread"; + usleep(totalEffectDuration * 1000); + if (callback != nullptr) { + LOG(VERBOSE) << "Notifying compose PWLE V2 complete"; + callback->onComplete(); + } + }).detach(); + + return ndk::ScopedAStatus::ok(); +} + } // namespace vibrator } // namespace hardware } // namespace android diff --git a/vibrator/aidl/default/include/vibrator-impl/Vibrator.h b/vibrator/aidl/default/include/vibrator-impl/Vibrator.h index e8f64cafe4..28bc763ffb 100644 --- a/vibrator/aidl/default/include/vibrator-impl/Vibrator.h +++ b/vibrator/aidl/default/include/vibrator-impl/Vibrator.h @@ -17,6 +17,7 @@ #pragma once #include +#include namespace aidl { namespace android { @@ -57,7 +58,18 @@ class Vibrator : public BnVibrator { ndk::ScopedAStatus getSupportedBraking(std::vector* supported) override; ndk::ScopedAStatus composePwle(const std::vector &composite, const std::shared_ptr &callback) override; + ndk::ScopedAStatus getPwleV2FrequencyToOutputAccelerationMap( + std::vector* _aidl_return) override; + ndk::ScopedAStatus getPwleV2PrimitiveDurationMaxMillis(int32_t* maxDurationMs) override; + ndk::ScopedAStatus getPwleV2PrimitiveDurationMinMillis(int32_t* minDurationMs) override; + ndk::ScopedAStatus getPwleV2CompositionSizeMax(int32_t* maxSize) override; + ndk::ScopedAStatus composePwleV2(const std::vector& composite, + const std::shared_ptr& callback) override; + private: + mutable std::mutex mMutex; + int32_t mVersion GUARDED_BY(mMutex) = 0; // current Hal version + int32_t mCapabilities GUARDED_BY(mMutex) = 0; }; } // namespace vibrator diff --git a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp index 2502589f9f..ffd38b1cf4 100644 --- a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp +++ b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp @@ -27,8 +27,12 @@ #include #include #include +#include +#include +#include #include "persistable_bundle_utils.h" +#include "pwle_v2_utils.h" #include "test_utils.h" using aidl::android::hardware::vibrator::ActivePwle; @@ -42,12 +46,16 @@ using aidl::android::hardware::vibrator::EffectStrength; using aidl::android::hardware::vibrator::IVibrator; using aidl::android::hardware::vibrator::IVibratorManager; using aidl::android::hardware::vibrator::PrimitivePwle; +using aidl::android::hardware::vibrator::PwleV2OutputMapEntry; +using aidl::android::hardware::vibrator::PwleV2Primitive; using aidl::android::hardware::vibrator::VendorEffect; using aidl::android::os::PersistableBundle; using std::chrono::high_resolution_clock; using namespace ::std::chrono_literals; +namespace pwle_v2_utils = aidl::android::hardware::vibrator::testing::pwlev2; + const std::vector kEffects{ndk::enum_range().begin(), ndk::enum_range().end()}; const std::vector kEffectStrengths{ndk::enum_range().begin(), @@ -80,6 +88,8 @@ const std::vector kInvalidPrimitives = { // Timeout to wait for vibration callback completion. static constexpr std::chrono::milliseconds VIBRATION_CALLBACK_TIMEOUT = 100ms; +static constexpr int32_t VENDOR_EFFECTS_MIN_VERSION = 3; + static std::vector findVibratorManagerNames() { std::vector names; constexpr auto callback = [](const char* instance, void* context) { @@ -137,6 +147,7 @@ class VibratorAidl : public testing::TestWithParam> } ASSERT_NE(vibrator, nullptr); + EXPECT_OK(vibrator->getInterfaceVersion(&version)); EXPECT_OK(vibrator->getCapabilities(&capabilities)); } @@ -146,6 +157,7 @@ class VibratorAidl : public testing::TestWithParam> } std::shared_ptr vibrator; + int32_t version; int32_t capabilities; }; @@ -476,6 +488,10 @@ TEST_P(VibratorAidl, PerformVendorEffectInvalidScale) { } TEST_P(VibratorAidl, PerformVendorEffectUnsupported) { + if (version < VENDOR_EFFECTS_MIN_VERSION) { + EXPECT_EQ(capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS, 0) + << "Vibrator version " << version << " should not report vendor effects capability"; + } if (capabilities & IVibrator::CAP_PERFORM_VENDOR_EFFECTS) return; for (EffectStrength strength : kEffectStrengths) { @@ -1035,6 +1051,156 @@ TEST_P(VibratorAidl, ComposePwleSegmentDurationBoundary) { } } +TEST_P(VibratorAidl, PwleV2FrequencyToOutputAccelerationMapHasValidFrequencyRange) { + std::vector frequencyToOutputAccelerationMap; + ndk::ScopedAStatus status = + vibrator->getPwleV2FrequencyToOutputAccelerationMap(&frequencyToOutputAccelerationMap); + if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2) { + EXPECT_OK(std::move(status)); + ASSERT_FALSE(frequencyToOutputAccelerationMap.empty()); + auto sharpnessRange = + pwle_v2_utils::getPwleV2SharpnessRange(vibrator, frequencyToOutputAccelerationMap); + // Validate the curve provides a usable sharpness range, which is a range of frequencies + // that are supported by the device. + ASSERT_TRUE(sharpnessRange.first >= 0); + // Validate that the sharpness range is a valid interval, not a single point. + ASSERT_TRUE(sharpnessRange.first < sharpnessRange.second); + } else { + EXPECT_UNKNOWN_OR_UNSUPPORTED(std::move(status)); + } +} + +TEST_P(VibratorAidl, GetPwleV2PrimitiveDurationMaxMillis) { + int32_t durationMs; + ndk::ScopedAStatus status = vibrator->getPwleV2PrimitiveDurationMaxMillis(&durationMs); + if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2) { + EXPECT_OK(std::move(status)); + ASSERT_GT(durationMs, 0); // Ensure greater than zero + ASSERT_GE(durationMs, + pwle_v2_utils::COMPOSE_PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS); + } else { + EXPECT_UNKNOWN_OR_UNSUPPORTED(std::move(status)); + } +} + +TEST_P(VibratorAidl, GetPwleV2CompositionSizeMax) { + int32_t maxSize; + ndk::ScopedAStatus status = vibrator->getPwleV2CompositionSizeMax(&maxSize); + if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2) { + EXPECT_OK(std::move(status)); + ASSERT_GT(maxSize, 0); // Ensure greater than zero + ASSERT_GE(maxSize, pwle_v2_utils::COMPOSE_PWLE_V2_MIN_REQUIRED_SIZE); + } else { + EXPECT_UNKNOWN_OR_UNSUPPORTED(std::move(status)); + } +} + +TEST_P(VibratorAidl, GetPwleV2PrimitiveDurationMinMillis) { + int32_t durationMs; + ndk::ScopedAStatus status = vibrator->getPwleV2PrimitiveDurationMinMillis(&durationMs); + if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2) { + EXPECT_OK(std::move(status)); + ASSERT_GT(durationMs, 0); // Ensure greater than zero + ASSERT_LE(durationMs, pwle_v2_utils::COMPOSE_PWLE_V2_MAX_ALLOWED_PRIMITIVE_MIN_DURATION_MS); + } else { + EXPECT_UNKNOWN_OR_UNSUPPORTED(std::move(status)); + } +} + +TEST_P(VibratorAidl, ComposeValidPwleV2Effect) { + if (!(capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2)) { + GTEST_SKIP() << "PWLE V2 not supported, skipping test"; + return; + } + + EXPECT_OK(vibrator->composePwleV2(pwle_v2_utils::composeValidPwleV2Effect(vibrator), nullptr)); + EXPECT_OK(vibrator->off()); +} + +TEST_P(VibratorAidl, ComposeValidPwleV2EffectWithCallback) { + if (!(capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2)) { + GTEST_SKIP() << "PWLE V2 not supported, skipping test"; + return; + } + + std::promise completionPromise; + std::future completionFuture{completionPromise.get_future()}; + auto callback = ndk::SharedRefBase::make( + [&completionPromise] { completionPromise.set_value(); }); + + int32_t minDuration; + EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMinMillis(&minDuration)); + auto timeout = std::chrono::milliseconds(minDuration) + VIBRATION_CALLBACK_TIMEOUT; + float minFrequency = pwle_v2_utils::getPwleV2FrequencyMinHz(vibrator); + + EXPECT_OK(vibrator->composePwleV2( + {PwleV2Primitive(/*amplitude=*/0.5, minFrequency, minDuration)}, callback)); + EXPECT_EQ(completionFuture.wait_for(timeout), std::future_status::ready); + EXPECT_OK(vibrator->off()); +} + +TEST_P(VibratorAidl, composePwleV2EffectWithTooManyPoints) { + if (!(capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2)) { + GTEST_SKIP() << "PWLE V2 not supported, skipping test"; + return; + } + + EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2( + pwle_v2_utils::composePwleV2EffectWithTooManyPoints(vibrator), nullptr)); +} + +TEST_P(VibratorAidl, composeInvalidPwleV2Effect) { + if (!(capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS_V2)) { + GTEST_SKIP() << "PWLE V2 not supported, skipping test"; + return; + } + + // Retrieve min and max durations + int32_t minDurationMs, maxDurationMs; + EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMinMillis(&minDurationMs)); + EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMaxMillis(&maxDurationMs)); + + std::vector composePwle; + + // Negative amplitude + composePwle.push_back(PwleV2Primitive(/*amplitude=*/-0.8f, /*frequency=*/100, minDurationMs)); + EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(composePwle, nullptr)) + << "Composing PWLE V2 effect with negative amplitude should fail"; + composePwle.clear(); + + // Amplitude exceeding 1.0 + composePwle.push_back(PwleV2Primitive(/*amplitude=*/1.2f, /*frequency=*/100, minDurationMs)); + EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(composePwle, nullptr)) + << "Composing PWLE V2 effect with amplitude greater than 1.0 should fail"; + composePwle.clear(); + + // Duration exceeding maximum + composePwle.push_back( + PwleV2Primitive(/*amplitude=*/0.2f, /*frequency=*/100, maxDurationMs + 10)); + EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(composePwle, nullptr)) + << "Composing PWLE V2 effect with duration exceeding maximum should fail"; + composePwle.clear(); + + // Negative duration + composePwle.push_back(PwleV2Primitive(/*amplitude=*/0.2f, /*frequency=*/100, /*time=*/-1)); + EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(composePwle, nullptr)) + << "Composing PWLE V2 effect with negative duration should fail"; + composePwle.clear(); + + // Frequency below minimum + float minFrequency = pwle_v2_utils::getPwleV2FrequencyMinHz(vibrator); + composePwle.push_back(PwleV2Primitive(/*amplitude=*/0.2f, minFrequency - 1, minDurationMs)); + EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(composePwle, nullptr)) + << "Composing PWLE V2 effect with frequency below minimum should fail"; + composePwle.clear(); + + // Frequency above maximum + float maxFrequency = pwle_v2_utils::getPwleV2FrequencyMaxHz(vibrator); + composePwle.push_back(PwleV2Primitive(/*amplitude=*/0.2f, maxFrequency + 1, minDurationMs)); + EXPECT_ILLEGAL_ARGUMENT(vibrator->composePwleV2(composePwle, nullptr)) + << "Composing PWLE V2 effect with frequency above maximum should fail"; +} + std::vector> GenerateVibratorMapping() { std::vector> tuples; diff --git a/vibrator/aidl/vts/pwle_v2_utils.h b/vibrator/aidl/vts/pwle_v2_utils.h new file mode 100644 index 0000000000..feb8790715 --- /dev/null +++ b/vibrator/aidl/vts/pwle_v2_utils.h @@ -0,0 +1,219 @@ +/* + * 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 VIBRATOR_HAL_PWLE_V2_UTILS_H +#define VIBRATOR_HAL_PWLE_V2_UTILS_H + +#include +#include "test_utils.h" + +using aidl::android::hardware::vibrator::IVibrator; +using aidl::android::hardware::vibrator::PwleV2OutputMapEntry; +using aidl::android::hardware::vibrator::PwleV2Primitive; + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { +namespace testing { +namespace pwlev2 { + +static constexpr int32_t COMPOSE_PWLE_V2_MIN_REQUIRED_SIZE = 16; +static constexpr int32_t COMPOSE_PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS = 1000; +static constexpr int32_t COMPOSE_PWLE_V2_MAX_ALLOWED_PRIMITIVE_MIN_DURATION_MS = 20; +static constexpr int32_t COMPOSE_PWLE_V2_MIN_REQUIRED_SENSITIVITY_DB_SL = 10; + +namespace { +/** + * Returns a vector of (frequency in Hz, acceleration in dB) pairs, where the acceleration + * value denotes the minimum output required at the corresponding frequency to be perceptible + * by a human. + */ +static std::vector> getMinPerceptibleLevel() { + return {{0.4f, -97.81f}, {2.0f, -69.86f}, {3.0f, -62.81f}, {4.0f, -58.81f}, + {5.0f, -56.69f}, {6.0f, -54.77f}, {7.2f, -52.85f}, {8.0f, -51.77f}, + {8.64f, -50.84f}, {10.0f, -48.90f}, {10.37f, -48.52f}, {12.44f, -46.50f}, + {14.93f, -44.43f}, {15.0f, -44.35f}, {17.92f, -41.96f}, {20.0f, -40.36f}, + {21.5f, -39.60f}, {25.0f, -37.48f}, {25.8f, -36.93f}, {30.0f, -34.31f}, + {35.0f, -33.13f}, {40.0f, -32.81f}, {50.0f, -31.94f}, {60.0f, -31.77f}, + {70.0f, -31.59f}, {72.0f, -31.55f}, {80.0f, -31.77f}, {86.4f, -31.94f}, + {90.0f, -31.73f}, {100.0f, -31.90f}, {103.68f, -31.77f}, {124.42f, -31.70f}, + {149.3f, -31.38f}, {150.0f, -31.35f}, {179.16f, -31.02f}, {200.0f, -30.86f}, + {215.0f, -30.35f}, {250.0f, -28.98f}, {258.0f, -28.68f}, {300.0f, -26.81f}, + {400.0f, -19.81f}}; +} + +static float interpolateLinearly(const std::vector& xAxis, const std::vector& yAxis, + float x) { + EXPECT_TRUE(!xAxis.empty()); + EXPECT_TRUE(xAxis.size() == yAxis.size()); + + if (x <= xAxis.front()) return yAxis.front(); + if (x >= xAxis.back()) return yAxis.back(); + + auto it = std::upper_bound(xAxis.begin(), xAxis.end(), x); + int i = std::distance(xAxis.begin(), it) - 1; // Index of the lower bound + + const float& x0 = xAxis[i]; + const float& y0 = yAxis[i]; + const float& x1 = xAxis[i + 1]; + const float& y1 = yAxis[i + 1]; + + return y0 + (x - x0) * (y1 - y0) / (x1 - x0); +} + +static float minPerceptibleDbCurve(float frequency) { + // Initialize minPerceptibleMap only once + static auto minPerceptibleMap = []() -> std::function { + static std::vector minPerceptibleFrequencies; + static std::vector minPerceptibleAccelerations; + + auto minPerceptibleLevel = getMinPerceptibleLevel(); + // Sort the 'minPerceptibleLevel' data in ascending order based on the + // frequency values (first element of each pair). + std::sort(minPerceptibleLevel.begin(), minPerceptibleLevel.end(), + [](const auto& a, const auto& b) { return a.first < b.first; }); + + for (const auto& entry : minPerceptibleLevel) { + minPerceptibleFrequencies.push_back(entry.first); + minPerceptibleAccelerations.push_back(entry.second); + } + + return [&](float freq) { + return interpolateLinearly(minPerceptibleFrequencies, minPerceptibleAccelerations, + freq); + }; + }(); + + return minPerceptibleMap(frequency); +} + +static float convertSensitivityLevelToDecibel(int sl, float frequency) { + return sl + minPerceptibleDbCurve(frequency); +} + +static float convertDecibelToAcceleration(float db) { + return std::pow(10.0f, db / 20.0f); +} +} // namespace + +static float convertSensitivityLevelToAcceleration(int sl, float frequency) { + return pwlev2::convertDecibelToAcceleration( + pwlev2::convertSensitivityLevelToDecibel(sl, frequency)); +} + +static float getPwleV2FrequencyMinHz(const std::shared_ptr& vibrator) { + std::vector frequencyToOutputAccelerationMap; + EXPECT_OK( + vibrator->getPwleV2FrequencyToOutputAccelerationMap(&frequencyToOutputAccelerationMap)); + EXPECT_TRUE(!frequencyToOutputAccelerationMap.empty()); + + auto entry = std::min_element( + frequencyToOutputAccelerationMap.begin(), frequencyToOutputAccelerationMap.end(), + [](const auto& a, const auto& b) { return a.frequencyHz < b.frequencyHz; }); + + return entry->frequencyHz; +} + +static float getPwleV2FrequencyMaxHz(const std::shared_ptr& vibrator) { + std::vector frequencyToOutputAccelerationMap; + EXPECT_OK( + vibrator->getPwleV2FrequencyToOutputAccelerationMap(&frequencyToOutputAccelerationMap)); + EXPECT_TRUE(!frequencyToOutputAccelerationMap.empty()); + + auto entry = std::max_element( + frequencyToOutputAccelerationMap.begin(), frequencyToOutputAccelerationMap.end(), + [](const auto& a, const auto& b) { return a.frequencyHz < b.frequencyHz; }); + + return entry->frequencyHz; +} + +static std::vector composeValidPwleV2Effect( + const std::shared_ptr& vibrator) { + int32_t minDurationMs; + EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMinMillis(&minDurationMs)); + int32_t maxDurationMs; + EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMaxMillis(&maxDurationMs)); + float minFrequency = getPwleV2FrequencyMinHz(vibrator); + float maxFrequency = getPwleV2FrequencyMaxHz(vibrator); + int32_t maxCompositionSize; + EXPECT_OK(vibrator->getPwleV2CompositionSizeMax(&maxCompositionSize)); + + std::vector pwleEffect; + + pwleEffect.emplace_back(0.1f, minFrequency, minDurationMs); + pwleEffect.emplace_back(0.5f, maxFrequency, maxDurationMs); + + float variedFrequency = (minFrequency + maxFrequency) / 2.0f; + for (int i = 0; i < maxCompositionSize - 2; i++) { + pwleEffect.emplace_back(0.7f, variedFrequency, minDurationMs); + } + + return pwleEffect; +} + +static std::vector composePwleV2EffectWithTooManyPoints( + const std::shared_ptr& vibrator) { + int32_t minDurationMs, maxCompositionSize; + EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMinMillis(&minDurationMs)); + EXPECT_OK(vibrator->getPwleV2CompositionSizeMax(&maxCompositionSize)); + float maxFrequency = getPwleV2FrequencyMaxHz(vibrator); + + std::vector pwleEffect(maxCompositionSize + 1); // +1 to exceed the limit + + std::fill(pwleEffect.begin(), pwleEffect.end(), + PwleV2Primitive(/*amplitude=*/0.2f, maxFrequency, minDurationMs)); + + return pwleEffect; +} + +static std::pair getPwleV2SharpnessRange( + const std::shared_ptr& vibrator, + std::vector freqToOutputAccelerationMap) { + std::pair sharpnessRange = {-1, -1}; + + // Sort the entries by frequency in ascending order + std::sort(freqToOutputAccelerationMap.begin(), freqToOutputAccelerationMap.end(), + [](const auto& a, const auto& b) { return a.frequencyHz < b.frequencyHz; }); + + for (const auto& entry : freqToOutputAccelerationMap) { + float minAcceptableOutputAcceleration = convertSensitivityLevelToAcceleration( + pwlev2::COMPOSE_PWLE_V2_MIN_REQUIRED_SENSITIVITY_DB_SL, entry.frequencyHz); + + if (sharpnessRange.first < 0 && + minAcceptableOutputAcceleration <= entry.maxOutputAccelerationGs) { + sharpnessRange.first = entry.frequencyHz; // Found the lower bound + } else if (sharpnessRange.first >= 0 && + minAcceptableOutputAcceleration >= entry.maxOutputAccelerationGs) { + sharpnessRange.second = entry.frequencyHz; // Found the upper bound + return sharpnessRange; + } + } + + if (sharpnessRange.first >= 0) { + // If only the lower bound was found, set the upper bound to the max frequency. + sharpnessRange.second = getPwleV2FrequencyMaxHz(vibrator); + } + + return sharpnessRange; +} +} // namespace pwlev2 +} // namespace testing +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl +#endif // VIBRATOR_HAL_PWLE_V2_UTILS_H