Fix haptics scaling to match VibrationEffect function
Fix the scale function in ExternalVibrationUtils to match the one used
by VibrationEffect. Add flagged fix with new tests for the module
libvibrator.
Bug: 356144312
Flag: android.os.vibrator.fix_audio_coupled_haptics_scaling
Test: libvibrator_test
Change-Id: I56116536d7cddab29448f0e436ee752c7181eb45
Merged-In: I56116536d7cddab29448f0e436ee752c7181eb45
diff --git a/libs/vibrator/Android.bp b/libs/vibrator/Android.bp
index 2af51a7..d3b3a73 100644
--- a/libs/vibrator/Android.bp
+++ b/libs/vibrator/Android.bp
@@ -24,6 +24,10 @@
cc_defaults {
name: "libvibrator_defaults",
+ defaults: [
+ "aconfig_lib_cc_shared_link.defaults",
+ ],
+
cflags: [
"-Wall",
"-Werror",
@@ -50,9 +54,11 @@
"libbinder",
"liblog",
"libutils",
+ "server_configurable_flags",
],
whole_static_libs: [
+ "android.os.vibrator.flags-aconfig-cc",
"libvibratorutils",
],
@@ -79,8 +85,14 @@
vendor_available: true,
double_loadable: true,
+ static_libs: [
+ "android.os.vibrator.flags-aconfig-cc",
+ ],
+
shared_libs: [
+ "liblog",
"libutils",
+ "server_configurable_flags",
],
srcs: [
@@ -89,6 +101,7 @@
visibility: [
"//frameworks/native/libs/vibrator",
+ "//frameworks/native/libs/vibrator/tests",
"//frameworks/av/media/libeffects/hapticgenerator",
],
}
diff --git a/libs/vibrator/ExternalVibrationUtils.cpp b/libs/vibrator/ExternalVibrationUtils.cpp
index 761ac1b..706f3d7 100644
--- a/libs/vibrator/ExternalVibrationUtils.cpp
+++ b/libs/vibrator/ExternalVibrationUtils.cpp
@@ -15,6 +15,9 @@
*/
#include <cstring>
+#include <android_os_vibrator.h>
+
+#include <algorithm>
#include <math.h>
#include <vibrator/ExternalVibrationUtils.h>
@@ -25,8 +28,9 @@
static constexpr float HAPTIC_SCALE_VERY_LOW_RATIO = 2.0f / 3.0f;
static constexpr float HAPTIC_SCALE_LOW_RATIO = 3.0f / 4.0f;
static constexpr float HAPTIC_MAX_AMPLITUDE_FLOAT = 1.0f;
+static constexpr float SCALE_GAMMA = 0.65f; // Same as VibrationEffect.SCALE_GAMMA
-float getHapticScaleGamma(HapticLevel level) {
+float getOldHapticScaleGamma(HapticLevel level) {
switch (level) {
case HapticLevel::VERY_LOW:
return 2.0f;
@@ -41,7 +45,7 @@
}
}
-float getHapticMaxAmplitudeRatio(HapticLevel level) {
+float getOldHapticMaxAmplitudeRatio(HapticLevel level) {
switch (level) {
case HapticLevel::VERY_LOW:
return HAPTIC_SCALE_VERY_LOW_RATIO;
@@ -56,6 +60,52 @@
}
}
+/* Same as VibrationScaler.SCALE_LEVEL_* */
+float getHapticScaleFactor(HapticLevel level) {
+ switch (level) {
+ case HapticLevel::VERY_LOW:
+ return 0.6f;
+ case HapticLevel::LOW:
+ return 0.8f;
+ case HapticLevel::HIGH:
+ return 1.2f;
+ case HapticLevel::VERY_HIGH:
+ return 1.4f;
+ default:
+ return 1.0f;
+ }
+}
+
+float applyOldHapticScale(float value, float gamma, float maxAmplitudeRatio) {
+ float sign = value >= 0 ? 1.0 : -1.0;
+ return powf(fabsf(value / HAPTIC_MAX_AMPLITUDE_FLOAT), gamma)
+ * maxAmplitudeRatio * HAPTIC_MAX_AMPLITUDE_FLOAT * sign;
+}
+
+float applyNewHapticScale(float value, float scaleFactor) {
+ float scale = powf(scaleFactor, 1.0f / SCALE_GAMMA);
+ if (scaleFactor <= 1) {
+ // Scale down is simply a gamma corrected application of scaleFactor to the intensity.
+ // Scale up requires a different curve to ensure the intensity will not become > 1.
+ return value * scale;
+ }
+
+ float sign = value >= 0 ? 1.0f : -1.0f;
+ float extraScale = powf(scaleFactor, 4.0f - scaleFactor);
+ float x = fabsf(value) * scale * extraScale;
+ float maxX = scale * extraScale; // scaled x for intensity == 1
+
+ float expX = expf(x);
+ float expMaxX = expf(maxX);
+
+ // Using f = tanh as the scale up function so the max value will converge.
+ // a = 1/f(maxX), used to scale f so that a*f(maxX) = 1 (the value will converge to 1).
+ float a = (expMaxX + 1.0f) / (expMaxX - 1.0f);
+ float fx = (expX - 1.0f) / (expX + 1.0f);
+
+ return sign * std::clamp(a * fx, 0.0f, 1.0f);
+}
+
void applyHapticScale(float* buffer, size_t length, HapticScale scale) {
if (scale.isScaleMute()) {
memset(buffer, 0, length * sizeof(float));
@@ -65,15 +115,18 @@
return;
}
HapticLevel hapticLevel = scale.getLevel();
+ float scaleFactor = getHapticScaleFactor(hapticLevel);
float adaptiveScaleFactor = scale.getAdaptiveScaleFactor();
- float gamma = getHapticScaleGamma(hapticLevel);
- float maxAmplitudeRatio = getHapticMaxAmplitudeRatio(hapticLevel);
+ float oldGamma = getOldHapticScaleGamma(hapticLevel);
+ float oldMaxAmplitudeRatio = getOldHapticMaxAmplitudeRatio(hapticLevel);
for (size_t i = 0; i < length; i++) {
if (hapticLevel != HapticLevel::NONE) {
- float sign = buffer[i] >= 0 ? 1.0 : -1.0;
- buffer[i] = powf(fabsf(buffer[i] / HAPTIC_MAX_AMPLITUDE_FLOAT), gamma)
- * maxAmplitudeRatio * HAPTIC_MAX_AMPLITUDE_FLOAT * sign;
+ if (android_os_vibrator_fix_audio_coupled_haptics_scaling()) {
+ buffer[i] = applyNewHapticScale(buffer[i], scaleFactor);
+ } else {
+ buffer[i] = applyOldHapticScale(buffer[i], oldGamma, oldMaxAmplitudeRatio);
+ }
}
if (adaptiveScaleFactor != 1.0f) {
diff --git a/libs/vibrator/TEST_MAPPING b/libs/vibrator/TEST_MAPPING
new file mode 100644
index 0000000..d782b43
--- /dev/null
+++ b/libs/vibrator/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "libvibrator_test"
+ }
+ ]
+}
diff --git a/libs/vibrator/tests/Android.bp b/libs/vibrator/tests/Android.bp
new file mode 100644
index 0000000..2921a62
--- /dev/null
+++ b/libs/vibrator/tests/Android.bp
@@ -0,0 +1,53 @@
+// 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 {
+ default_team: "trendy_team_haptics_framework",
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_native_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_test {
+ name: "libvibrator_test",
+ test_suites: ["general-tests"],
+ defaults: [
+ "aconfig_lib_cc_shared_link.defaults",
+ ],
+ srcs: [
+ "ExternalVibrationTest.cpp",
+ "ExternalVibrationUtilsTest.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+ static_libs: [
+ "android.os.vibrator.flags-aconfig-cc",
+ "libflagtest",
+ "libgtest",
+ "liblog",
+ "libvibrator",
+ "libvibratorutils",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libutils",
+ "server_configurable_flags",
+ ],
+}
diff --git a/libs/vibrator/tests/ExternalVibrationTest.cpp b/libs/vibrator/tests/ExternalVibrationTest.cpp
new file mode 100644
index 0000000..3141380
--- /dev/null
+++ b/libs/vibrator/tests/ExternalVibrationTest.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/Parcel.h>
+#include <gtest/gtest.h>
+#include <vibrator/ExternalVibration.h>
+
+using namespace android;
+using namespace testing;
+
+using HapticLevel = os::HapticLevel;
+using ScaleLevel = os::ExternalVibrationScale::ScaleLevel;
+
+class TestVibrationController : public os::IExternalVibrationController {
+public:
+ explicit TestVibrationController() {}
+ IBinder *onAsBinder() override { return nullptr; }
+ binder::Status mute(/*out*/ bool *ret) override {
+ *ret = false;
+ return binder::Status::ok();
+ };
+ binder::Status unmute(/*out*/ bool *ret) override {
+ *ret = false;
+ return binder::Status::ok();
+ };
+};
+
+class ExternalVibrationTest : public Test {
+protected:
+ HapticLevel toHapticLevel(ScaleLevel level) {
+ os::ExternalVibrationScale externalVibrationScale;
+ externalVibrationScale.scaleLevel = level;
+ os::HapticScale hapticScale =
+ os::ExternalVibration::externalVibrationScaleToHapticScale(externalVibrationScale);
+ return hapticScale.getLevel();
+ }
+};
+
+TEST_F(ExternalVibrationTest, TestReadAndWriteToParcel) {
+ int32_t uid = 1;
+ std::string pkg("package.name");
+ audio_attributes_t originalAttrs;
+ originalAttrs.content_type = AUDIO_CONTENT_TYPE_SONIFICATION;
+ originalAttrs.usage = AUDIO_USAGE_ASSISTANCE_SONIFICATION;
+ originalAttrs.source = AUDIO_SOURCE_VOICE_COMMUNICATION;
+ originalAttrs.flags = AUDIO_FLAG_BYPASS_MUTE;
+ sp<TestVibrationController> vibrationController = new TestVibrationController();
+ ASSERT_NE(vibrationController, nullptr);
+ sp<os::ExternalVibration> original =
+ new os::ExternalVibration(uid, pkg, originalAttrs, vibrationController);
+ ASSERT_NE(original, nullptr);
+ EXPECT_EQ(original->getUid(), uid);
+ EXPECT_EQ(original->getPackage(), pkg);
+ EXPECT_EQ(original->getAudioAttributes().content_type, originalAttrs.content_type);
+ EXPECT_EQ(original->getAudioAttributes().usage, originalAttrs.usage);
+ EXPECT_EQ(original->getAudioAttributes().source, originalAttrs.source);
+ EXPECT_EQ(original->getAudioAttributes().flags, originalAttrs.flags);
+ EXPECT_EQ(original->getController(), vibrationController);
+ audio_attributes_t defaultAttrs;
+ defaultAttrs.content_type = AUDIO_CONTENT_TYPE_UNKNOWN;
+ defaultAttrs.usage = AUDIO_USAGE_UNKNOWN;
+ defaultAttrs.source = AUDIO_SOURCE_DEFAULT;
+ defaultAttrs.flags = AUDIO_FLAG_NONE;
+ sp<os::ExternalVibration> parceled =
+ new os::ExternalVibration(0, std::string(""), defaultAttrs, nullptr);
+ ASSERT_NE(parceled, nullptr);
+ Parcel parcel;
+ original->writeToParcel(&parcel);
+ parcel.setDataPosition(0);
+ parceled->readFromParcel(&parcel);
+ EXPECT_EQ(parceled->getUid(), uid);
+ EXPECT_EQ(parceled->getPackage(), pkg);
+ EXPECT_EQ(parceled->getAudioAttributes().content_type, originalAttrs.content_type);
+ EXPECT_EQ(parceled->getAudioAttributes().usage, originalAttrs.usage);
+ EXPECT_EQ(parceled->getAudioAttributes().source, originalAttrs.source);
+ EXPECT_EQ(parceled->getAudioAttributes().flags, originalAttrs.flags);
+ // TestVibrationController does not implement onAsBinder, skip controller parcel in this test.
+}
+
+TEST_F(ExternalVibrationTest, TestExternalVibrationScaleToHapticScale) {
+ os::ExternalVibrationScale externalVibrationScale;
+ externalVibrationScale.scaleLevel = ScaleLevel::SCALE_HIGH;
+ externalVibrationScale.adaptiveHapticsScale = 0.8f;
+ os::HapticScale hapticScale =
+ os::ExternalVibration::externalVibrationScaleToHapticScale(externalVibrationScale);
+ // Check scale factor is forwarded.
+ EXPECT_EQ(hapticScale.getLevel(), HapticLevel::HIGH);
+ EXPECT_EQ(hapticScale.getAdaptiveScaleFactor(), 0.8f);
+ // Check conversion for all levels.
+ EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_MUTE), HapticLevel::MUTE);
+ EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_VERY_LOW), HapticLevel::VERY_LOW);
+ EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_LOW), HapticLevel::LOW);
+ EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_NONE), HapticLevel::NONE);
+ EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_HIGH), HapticLevel::HIGH);
+ EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_VERY_HIGH), HapticLevel::VERY_HIGH);
+}
diff --git a/libs/vibrator/tests/ExternalVibrationUtilsTest.cpp b/libs/vibrator/tests/ExternalVibrationUtilsTest.cpp
new file mode 100644
index 0000000..3d8dd9c
--- /dev/null
+++ b/libs/vibrator/tests/ExternalVibrationUtilsTest.cpp
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android_os_vibrator.h>
+#include <flag_macros.h>
+#include <gtest/gtest.h>
+#include <vibrator/ExternalVibrationUtils.h>
+
+#include "test_utils.h"
+
+#define FLAG_NS android::os::vibrator
+
+using namespace android;
+using namespace testing;
+
+using HapticScale = os::HapticScale;
+using HapticLevel = os::HapticLevel;
+
+static constexpr float TEST_TOLERANCE = 1e-2f;
+static constexpr size_t TEST_BUFFER_LENGTH = 4;
+static float TEST_BUFFER[TEST_BUFFER_LENGTH] = { 1, -1, 0.5f, -0.2f };
+
+class ExternalVibrationUtilsTest : public Test {
+public:
+ void SetUp() override {
+ std::copy(std::begin(TEST_BUFFER), std::end(TEST_BUFFER), std::begin(mBuffer));
+ }
+
+protected:
+ void scaleBuffer(HapticLevel hapticLevel) {
+ scaleBuffer(HapticScale(hapticLevel), 0 /* limit */);
+ }
+
+ void scaleBuffer(HapticLevel hapticLevel, float adaptiveScaleFactor) {
+ scaleBuffer(hapticLevel, adaptiveScaleFactor, 0 /* limit */);
+ }
+
+ void scaleBuffer(HapticLevel hapticLevel, float adaptiveScaleFactor, float limit) {
+ scaleBuffer(HapticScale(hapticLevel, adaptiveScaleFactor), limit);
+ }
+
+ void scaleBuffer(HapticScale hapticScale, float limit) {
+ std::copy(std::begin(TEST_BUFFER), std::end(TEST_BUFFER), std::begin(mBuffer));
+ os::scaleHapticData(&mBuffer[0], TEST_BUFFER_LENGTH, hapticScale, limit);
+ }
+
+ float mBuffer[TEST_BUFFER_LENGTH];
+};
+
+TEST_F_WITH_FLAGS(
+ ExternalVibrationUtilsTest,
+ TestLegacyScaleMute,
+ REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling))
+) {
+ float expected[TEST_BUFFER_LENGTH];
+ std::fill(std::begin(expected), std::end(expected), 0);
+
+ scaleBuffer(HapticLevel::MUTE);
+ EXPECT_FLOATS_NEARLY_EQ(expected, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+ ExternalVibrationUtilsTest,
+ TestFixedScaleMute,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling))
+) {
+ float expected[TEST_BUFFER_LENGTH];
+ std::fill(std::begin(expected), std::end(expected), 0);
+
+ scaleBuffer(HapticLevel::MUTE);
+ EXPECT_FLOATS_NEARLY_EQ(expected, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+ ExternalVibrationUtilsTest,
+ TestLegacyScaleNone,
+ REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling))
+) {
+ float expected[TEST_BUFFER_LENGTH];
+ std::copy(std::begin(TEST_BUFFER), std::end(TEST_BUFFER), std::begin(expected));
+
+ scaleBuffer(HapticLevel::NONE);
+ EXPECT_FLOATS_NEARLY_EQ(expected, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+ ExternalVibrationUtilsTest,
+ TestFixedScaleNone,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling))
+) {
+ float expected[TEST_BUFFER_LENGTH];
+ std::copy(std::begin(TEST_BUFFER), std::end(TEST_BUFFER), std::begin(expected));
+
+ scaleBuffer(HapticLevel::NONE);
+ EXPECT_FLOATS_NEARLY_EQ(expected, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+ ExternalVibrationUtilsTest,
+ TestLegacyScaleToHapticLevel,
+ REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling))
+) {
+ float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 1, -1, 0.84f, -0.66f };
+ scaleBuffer(HapticLevel::VERY_HIGH);
+ EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ float expectedHigh[TEST_BUFFER_LENGTH] = { 1, -1, 0.7f, -0.44f };
+ scaleBuffer(HapticLevel::HIGH);
+ EXPECT_FLOATS_NEARLY_EQ(expectedHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ float expectedLow[TEST_BUFFER_LENGTH] = { 0.75f, -0.75f, 0.26f, -0.06f };
+ scaleBuffer(HapticLevel::LOW);
+ EXPECT_FLOATS_NEARLY_EQ(expectedLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ float expectedVeryLow[TEST_BUFFER_LENGTH] = { 0.66f, -0.66f, 0.16f, -0.02f };
+ scaleBuffer(HapticLevel::VERY_LOW);
+ EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+ ExternalVibrationUtilsTest,
+ TestFixedScaleToHapticLevel,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling))
+) {
+ float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 1, -1, 0.79f, -0.39f };
+ scaleBuffer(HapticLevel::VERY_HIGH);
+ EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ float expectedHigh[TEST_BUFFER_LENGTH] = { 1, -1, 0.62f, -0.27f };
+ scaleBuffer(HapticLevel::HIGH);
+ EXPECT_FLOATS_NEARLY_EQ(expectedHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ float expectedLow[TEST_BUFFER_LENGTH] = { 0.70f, -0.70f, 0.35f, -0.14f };
+ scaleBuffer(HapticLevel::LOW);
+ EXPECT_FLOATS_NEARLY_EQ(expectedLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ float expectedVeryLow[TEST_BUFFER_LENGTH] = { 0.45f, -0.45f, 0.22f, -0.09f };
+ scaleBuffer(HapticLevel::VERY_LOW);
+ EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+ ExternalVibrationUtilsTest,
+ TestAdaptiveScaleFactorAppliedAfterLegacyScale,
+ REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling))
+) {
+ // Haptic level scale up then adaptive scale down
+ float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 0.2, -0.2, 0.16f, -0.13f };
+ scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */);
+ EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ // Haptic level scale up then adaptive scale up
+ float expectedHigh[TEST_BUFFER_LENGTH] = { 1.5f, -1.5f, 1.06f, -0.67f };
+ scaleBuffer(HapticLevel::HIGH, 1.5f /* adaptiveScaleFactor */);
+ EXPECT_FLOATS_NEARLY_EQ(expectedHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ // Haptic level scale down then adaptive scale down
+ float expectedLow[TEST_BUFFER_LENGTH] = { 0.45f, -0.45f, 0.15f, -0.04f };
+ scaleBuffer(HapticLevel::LOW, 0.6f /* adaptiveScaleFactor */);
+ EXPECT_FLOATS_NEARLY_EQ(expectedLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ // Haptic level scale down then adaptive scale up
+ float expectedVeryLow[TEST_BUFFER_LENGTH] = { 1.33f, -1.33f, 0.33f, -0.05f };
+ scaleBuffer(HapticLevel::VERY_LOW, 2 /* adaptiveScaleFactor */);
+ EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+ ExternalVibrationUtilsTest,
+ TestAdaptiveScaleFactorAppliedAfterFixedScale,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling))
+) {
+ // Haptic level scale up then adaptive scale down
+ float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 0.2, -0.2, 0.16f, -0.07f };
+ scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */);
+ EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ // Haptic level scale up then adaptive scale up
+ float expectedHigh[TEST_BUFFER_LENGTH] = { 1.5f, -1.5f, 0.93f, -0.41f };
+ scaleBuffer(HapticLevel::HIGH, 1.5f /* adaptiveScaleFactor */);
+ EXPECT_FLOATS_NEARLY_EQ(expectedHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ // Haptic level scale down then adaptive scale down
+ float expectedLow[TEST_BUFFER_LENGTH] = { 0.42f, -0.42f, 0.21f, -0.08f };
+ scaleBuffer(HapticLevel::LOW, 0.6f /* adaptiveScaleFactor */);
+ EXPECT_FLOATS_NEARLY_EQ(expectedLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ // Haptic level scale down then adaptive scale up
+ float expectedVeryLow[TEST_BUFFER_LENGTH] = { 0.91f, -0.91f, 0.45f, -0.18f };
+ scaleBuffer(HapticLevel::VERY_LOW, 2 /* adaptiveScaleFactor */);
+ EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+ ExternalVibrationUtilsTest,
+ TestLimitAppliedAfterLegacyScale,
+ REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling))
+) {
+ // Scaled = { 0.2, -0.2, 0.16f, -0.13f };
+ float expectedClippedVeryHigh[TEST_BUFFER_LENGTH] = { 0.15f, -0.15f, 0.15f, -0.13f };
+ scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */, 0.15f /* limit */);
+ EXPECT_FLOATS_NEARLY_EQ(expectedClippedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ // Scaled = { 1, -1, 0.5f, -0.2f };
+ float expectedClippedVeryLow[TEST_BUFFER_LENGTH] = { 0.7f, -0.7f, 0.33f, -0.05f };
+ scaleBuffer(HapticLevel::VERY_LOW, 2 /* adaptiveScaleFactor */, 0.7f /* limit */);
+ EXPECT_FLOATS_NEARLY_EQ(expectedClippedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+ ExternalVibrationUtilsTest,
+ TestLimitAppliedAfterFixedScale,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling))
+) {
+ // Scaled = { 0.2, -0.2, 0.16f, -0.13f };
+ float expectedClippedVeryHigh[TEST_BUFFER_LENGTH] = { 0.15f, -0.15f, 0.15f, -0.07f };
+ scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */, 0.15f /* limit */);
+ EXPECT_FLOATS_NEARLY_EQ(expectedClippedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+ // Scaled = { 1, -1, 0.5f, -0.2f };
+ float expectedClippedVeryLow[TEST_BUFFER_LENGTH] = { 0.7f, -0.7f, 0.45f, -0.18f };
+ scaleBuffer(HapticLevel::VERY_LOW, 2 /* adaptiveScaleFactor */, 0.7f /* limit */);
+ EXPECT_FLOATS_NEARLY_EQ(expectedClippedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
diff --git a/libs/vibrator/tests/test_utils.h b/libs/vibrator/tests/test_utils.h
new file mode 100644
index 0000000..f491ea1
--- /dev/null
+++ b/libs/vibrator/tests/test_utils.h
@@ -0,0 +1,31 @@
+/*
+ * 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 LIBVIBRATOR_TEST_UTILS_H
+#define LIBVIBRATOR_TEST_UTILS_H
+
+#include <gtest/gtest.h>
+
+#if !defined(EXPECT_FLOATS_NEARLY_EQ)
+#define EXPECT_FLOATS_NEARLY_EQ(expected, actual, length, epsilon) \
+ for (size_t i = 0; i < length; i++) { \
+ EXPECT_NEAR(expected[i], actual[i], epsilon) << " at Index: " << i; \
+ }
+#else
+#error Macro EXPECT_FLOATS_NEARLY_EQ already defined
+#endif
+
+#endif //LIBVIBRATOR_TEST_UTILS_H