Merge "Update HAL with new PWLE V2 APIs" into main
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