Update HAL with new PWLE V2 APIs
Updating IVibrator.aidl to introduce APIs for PWLE V2 support. The updated interface now enables retrieval of minimum and maximum PWLE durations, maximum composition size, and facilitates the composition of PWLE effects using points.
Bug: 347034419
Flag: EXEMPT HAL interface change
Test: vts-tradefed run vts -m VtsHalVibratorTargetTest
Change-Id: Id0a93e4f8678622bdb698cddc2b39f46e0283fdd
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 af619c6..0dcc657 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 @@
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<android.hardware.vibrator.PwleV2OutputMapEntry> 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 @@
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 0000000..a5eda52
--- /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 <name>-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 0000000..c4f3ea9
--- /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 <name>-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 768ec4f..11f36ba 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.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 @@
* 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 @@
* - 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<PwleV2OutputMapEntry> 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 0000000..a8db87c
--- /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 0000000..bd7bec6
--- /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 29e7d18..4f020a0 100644
--- a/vibrator/aidl/default/Vibrator.cpp
+++ b/vibrator/aidl/default/Vibrator.cpp
@@ -27,9 +27,12 @@
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 @@
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::performVendorEffect(
const VendorEffect& effect, const std::shared_ptr<IVibratorCallback>& 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 @@
return ndk::ScopedAStatus::ok();
}
+ndk::ScopedAStatus Vibrator::getPwleV2FrequencyToOutputAccelerationMap(
+ std::vector<PwleV2OutputMapEntry>* _aidl_return) {
+ std::vector<PwleV2OutputMapEntry> frequencyToOutputAccelerationMap;
+
+ std::vector<std::pair<float, float>> 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<PwleV2OutputMapEntry> 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<PwleV2OutputMapEntry> 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<PwleV2Primitive>& composite,
+ const std::shared_ptr<IVibratorCallback>& 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<PwleV2OutputMapEntry> 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 e8f64ca..28bc763 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 <aidl/android/hardware/vibrator/BnVibrator.h>
+#include <android-base/thread_annotations.h>
namespace aidl {
namespace android {
@@ -57,7 +58,18 @@
ndk::ScopedAStatus getSupportedBraking(std::vector<Braking>* supported) override;
ndk::ScopedAStatus composePwle(const std::vector<PrimitivePwle> &composite,
const std::shared_ptr<IVibratorCallback> &callback) override;
+ ndk::ScopedAStatus getPwleV2FrequencyToOutputAccelerationMap(
+ std::vector<PwleV2OutputMapEntry>* _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<PwleV2Primitive>& composite,
+ const std::shared_ptr<IVibratorCallback>& 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 2502589..ffd38b1 100644
--- a/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp
+++ b/vibrator/aidl/vts/VtsHalVibratorTargetTest.cpp
@@ -27,8 +27,12 @@
#include <cstdlib>
#include <ctime>
#include <future>
+#include <iomanip>
+#include <iostream>
+#include <random>
#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::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<Effect> kEffects{ndk::enum_range<Effect>().begin(),
ndk::enum_range<Effect>().end()};
const std::vector<EffectStrength> kEffectStrengths{ndk::enum_range<EffectStrength>().begin(),
@@ -80,6 +88,8 @@
// 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<std::string> findVibratorManagerNames() {
std::vector<std::string> names;
constexpr auto callback = [](const char* instance, void* context) {
@@ -137,6 +147,7 @@
}
ASSERT_NE(vibrator, nullptr);
+ EXPECT_OK(vibrator->getInterfaceVersion(&version));
EXPECT_OK(vibrator->getCapabilities(&capabilities));
}
@@ -146,6 +157,7 @@
}
std::shared_ptr<IVibrator> vibrator;
+ int32_t version;
int32_t capabilities;
};
@@ -476,6 +488,10 @@
}
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, PwleV2FrequencyToOutputAccelerationMapHasValidFrequencyRange) {
+ std::vector<PwleV2OutputMapEntry> 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<void> completionPromise;
+ std::future<void> completionFuture{completionPromise.get_future()};
+ auto callback = ndk::SharedRefBase::make<CompletionCallback>(
+ [&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<PwleV2Primitive> 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<std::tuple<int32_t, int32_t>> GenerateVibratorMapping() {
std::vector<std::tuple<int32_t, int32_t>> tuples;
diff --git a/vibrator/aidl/vts/pwle_v2_utils.h b/vibrator/aidl/vts/pwle_v2_utils.h
new file mode 100644
index 0000000..feb8790
--- /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 <aidl/android/hardware/vibrator/IVibrator.h>
+#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<std::pair<float, float>> 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<float>& xAxis, const std::vector<float>& 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<float(float)> {
+ static std::vector<float> minPerceptibleFrequencies;
+ static std::vector<float> 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<IVibrator>& vibrator) {
+ std::vector<PwleV2OutputMapEntry> 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<IVibrator>& vibrator) {
+ std::vector<PwleV2OutputMapEntry> 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<PwleV2Primitive> composeValidPwleV2Effect(
+ const std::shared_ptr<IVibrator>& 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<PwleV2Primitive> 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<PwleV2Primitive> composePwleV2EffectWithTooManyPoints(
+ const std::shared_ptr<IVibrator>& vibrator) {
+ int32_t minDurationMs, maxCompositionSize;
+ EXPECT_OK(vibrator->getPwleV2PrimitiveDurationMinMillis(&minDurationMs));
+ EXPECT_OK(vibrator->getPwleV2CompositionSizeMax(&maxCompositionSize));
+ float maxFrequency = getPwleV2FrequencyMaxHz(vibrator);
+
+ std::vector<PwleV2Primitive> 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<float, float> getPwleV2SharpnessRange(
+ const std::shared_ptr<IVibrator>& vibrator,
+ std::vector<PwleV2OutputMapEntry> freqToOutputAccelerationMap) {
+ std::pair<float, float> 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